develop-updateui #1
85
bun.lock
85
bun.lock
@@ -15,7 +15,6 @@
|
|||||||
"@unhead/vue": "^2.1.1",
|
"@unhead/vue": "^2.1.1",
|
||||||
"@vueuse/core": "^14.1.0",
|
"@vueuse/core": "^14.1.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"firebase": "^12.8.0",
|
|
||||||
"firebase-admin": "^13.6.0",
|
"firebase-admin": "^13.6.0",
|
||||||
"hono": "^4.11.3",
|
"hono": "^4.11.3",
|
||||||
"is-mobile": "^5.0.0",
|
"is-mobile": "^5.0.0",
|
||||||
@@ -258,94 +257,24 @@
|
|||||||
|
|
||||||
"@fastify/busboy": ["@fastify/busboy@3.2.0", "", {}, "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA=="],
|
"@fastify/busboy": ["@fastify/busboy@3.2.0", "", {}, "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA=="],
|
||||||
|
|
||||||
"@firebase/ai": ["@firebase/ai@2.7.0", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.3", "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-types": "0.x" } }, "sha512-PwpCz+TtAMWICM7uQNO0mkSPpUKwrMV4NSwHkbVKDvPKoaQmSlO96vIz+Suw2Ao1EaUUsxYb5LGImHWt/fSnRQ=="],
|
|
||||||
|
|
||||||
"@firebase/analytics": ["@firebase/analytics@0.10.19", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/installations": "0.6.19", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-3wU676fh60gaiVYQEEXsbGS4HbF2XsiBphyvvqDbtC1U4/dO4coshbYktcCHq+HFaGIK07iHOh4pME0hEq1fcg=="],
|
|
||||||
|
|
||||||
"@firebase/analytics-compat": ["@firebase/analytics-compat@0.2.25", "", { "dependencies": { "@firebase/analytics": "0.10.19", "@firebase/analytics-types": "0.8.3", "@firebase/component": "0.7.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-fdzoaG0BEKbqksRDhmf4JoyZf16Wosrl0Y7tbZtJyVDOOwziE0vrFjmZuTdviL0yhak+Nco6rMsUUbkbD+qb6Q=="],
|
|
||||||
|
|
||||||
"@firebase/analytics-types": ["@firebase/analytics-types@0.8.3", "", {}, "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg=="],
|
|
||||||
|
|
||||||
"@firebase/app": ["@firebase/app@0.14.7", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "idb": "7.1.1", "tslib": "^2.1.0" } }, "sha512-o3ZfnOx0AWBD5n/36p2zPoB0rDDxQP8H/A60zDLvvfRLtW8b3LfCyV97GKpJaAVV1JMMl/BC89EDzMyzxFZxTw=="],
|
|
||||||
|
|
||||||
"@firebase/app-check": ["@firebase/app-check@0.11.0", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-XAvALQayUMBJo58U/rxW02IhsesaxxfWVmVkauZvGEz3vOAjMEQnzFlyblqkc2iAaO82uJ2ZVyZv9XzPfxjJ6w=="],
|
|
||||||
|
|
||||||
"@firebase/app-check-compat": ["@firebase/app-check-compat@0.4.0", "", { "dependencies": { "@firebase/app-check": "0.11.0", "@firebase/app-check-types": "0.5.3", "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-UfK2Q8RJNjYM/8MFORltZRG9lJj11k0nW84rrffiKvcJxLf1jf6IEjCIkCamykHE73C6BwqhVfhIBs69GXQV0g=="],
|
|
||||||
|
|
||||||
"@firebase/app-check-interop-types": ["@firebase/app-check-interop-types@0.3.3", "", {}, "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A=="],
|
"@firebase/app-check-interop-types": ["@firebase/app-check-interop-types@0.3.3", "", {}, "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A=="],
|
||||||
|
|
||||||
"@firebase/app-check-types": ["@firebase/app-check-types@0.5.3", "", {}, "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng=="],
|
|
||||||
|
|
||||||
"@firebase/app-compat": ["@firebase/app-compat@0.5.7", "", { "dependencies": { "@firebase/app": "0.14.7", "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" } }, "sha512-MO+jfap8IBZQ+K8L2QCiHObyMgpYHrxo4Hc7iJgfb9hjGRW/z1y6LWVdT9wBBK+VJ7cRP2DjAiWQP+thu53hHA=="],
|
|
||||||
|
|
||||||
"@firebase/app-types": ["@firebase/app-types@0.9.3", "", {}, "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw=="],
|
"@firebase/app-types": ["@firebase/app-types@0.9.3", "", {}, "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw=="],
|
||||||
|
|
||||||
"@firebase/auth": ["@firebase/auth@1.12.0", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@react-native-async-storage/async-storage": "^2.2.0" }, "optionalPeers": ["@react-native-async-storage/async-storage"] }, "sha512-zkvLpsrxynWHk07qGrUDfCSqKf4AvfZGEqJ7mVCtYGjNNDbGE71k0Yn84rg8QEZu4hQw1BC0qDEHzpNVBcSVmA=="],
|
|
||||||
|
|
||||||
"@firebase/auth-compat": ["@firebase/auth-compat@0.6.2", "", { "dependencies": { "@firebase/auth": "1.12.0", "@firebase/auth-types": "0.13.0", "@firebase/component": "0.7.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-8UhCzF6pav9bw/eXA8Zy1QAKssPRYEYXaWagie1ewLTwHkXv6bKp/j6/IwzSYQP67sy/BMFXIFaCCsoXzFLr7A=="],
|
|
||||||
|
|
||||||
"@firebase/auth-interop-types": ["@firebase/auth-interop-types@0.2.4", "", {}, "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA=="],
|
"@firebase/auth-interop-types": ["@firebase/auth-interop-types@0.2.4", "", {}, "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA=="],
|
||||||
|
|
||||||
"@firebase/auth-types": ["@firebase/auth-types@0.13.0", "", { "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg=="],
|
|
||||||
|
|
||||||
"@firebase/component": ["@firebase/component@0.7.0", "", { "dependencies": { "@firebase/util": "1.13.0", "tslib": "^2.1.0" } }, "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg=="],
|
"@firebase/component": ["@firebase/component@0.7.0", "", { "dependencies": { "@firebase/util": "1.13.0", "tslib": "^2.1.0" } }, "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg=="],
|
||||||
|
|
||||||
"@firebase/data-connect": ["@firebase/data-connect@0.3.12", "", { "dependencies": { "@firebase/auth-interop-types": "0.2.4", "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-baPddcoNLj/+vYo+HSJidJUdr5W4OkhT109c5qhR8T1dJoZcyJpkv/dFpYlw/VJ3dV66vI8GHQFrmAZw/xUS4g=="],
|
|
||||||
|
|
||||||
"@firebase/database": ["@firebase/database@1.1.0", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.3", "@firebase/auth-interop-types": "0.2.4", "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg=="],
|
"@firebase/database": ["@firebase/database@1.1.0", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.3", "@firebase/auth-interop-types": "0.2.4", "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg=="],
|
||||||
|
|
||||||
"@firebase/database-compat": ["@firebase/database-compat@2.1.0", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/database": "1.1.0", "@firebase/database-types": "1.0.16", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" } }, "sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg=="],
|
"@firebase/database-compat": ["@firebase/database-compat@2.1.0", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/database": "1.1.0", "@firebase/database-types": "1.0.16", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" } }, "sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg=="],
|
||||||
|
|
||||||
"@firebase/database-types": ["@firebase/database-types@1.0.16", "", { "dependencies": { "@firebase/app-types": "0.9.3", "@firebase/util": "1.13.0" } }, "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw=="],
|
"@firebase/database-types": ["@firebase/database-types@1.0.16", "", { "dependencies": { "@firebase/app-types": "0.9.3", "@firebase/util": "1.13.0" } }, "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw=="],
|
||||||
|
|
||||||
"@firebase/firestore": ["@firebase/firestore@4.10.0", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "@firebase/webchannel-wrapper": "1.0.5", "@grpc/grpc-js": "~1.9.0", "@grpc/proto-loader": "^0.7.8", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-fgF6EbpoagGWh5Vwfu/7/jYgBFwUCwTlPNVF/aSjHcoEDRXpRsIqVfAFTp1LD+dWAUcAKEK3h+osk8spMJXtxA=="],
|
|
||||||
|
|
||||||
"@firebase/firestore-compat": ["@firebase/firestore-compat@0.4.4", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/firestore": "4.10.0", "@firebase/firestore-types": "3.0.3", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-JvxxIgi+D5v9BecjLA1YomdyF7LA6CXhJuVK10b4GtRrB3m2O2hT1jJWbKYZYHUAjTaajkvnos+4U5VNxqkI2w=="],
|
|
||||||
|
|
||||||
"@firebase/firestore-types": ["@firebase/firestore-types@3.0.3", "", { "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q=="],
|
|
||||||
|
|
||||||
"@firebase/functions": ["@firebase/functions@0.13.1", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.3", "@firebase/auth-interop-types": "0.2.4", "@firebase/component": "0.7.0", "@firebase/messaging-interop-types": "0.2.3", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-sUeWSb0rw5T+6wuV2o9XNmh9yHxjFI9zVGFnjFi+n7drTEWpl7ZTz1nROgGrSu472r+LAaj+2YaSicD4R8wfbw=="],
|
|
||||||
|
|
||||||
"@firebase/functions-compat": ["@firebase/functions-compat@0.4.1", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/functions": "0.13.1", "@firebase/functions-types": "0.6.3", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-AxxUBXKuPrWaVNQ8o1cG1GaCAtXT8a0eaTDfqgS5VsRYLAR0ALcfqDLwo/QyijZj1w8Qf8n3Qrfy/+Im245hOQ=="],
|
|
||||||
|
|
||||||
"@firebase/functions-types": ["@firebase/functions-types@0.6.3", "", {}, "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg=="],
|
|
||||||
|
|
||||||
"@firebase/installations": ["@firebase/installations@0.6.19", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/util": "1.13.0", "idb": "7.1.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-nGDmiwKLI1lerhwfwSHvMR9RZuIH5/8E3kgUWnVRqqL7kGVSktjLTWEMva7oh5yxQ3zXfIlIwJwMcaM5bK5j8Q=="],
|
|
||||||
|
|
||||||
"@firebase/installations-compat": ["@firebase/installations-compat@0.2.19", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/installations": "0.6.19", "@firebase/installations-types": "0.5.3", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-khfzIY3EI5LePePo7vT19/VEIH1E3iYsHknI/6ek9T8QCozAZshWT9CjlwOzZrKvTHMeNcbpo/VSOSIWDSjWdQ=="],
|
|
||||||
|
|
||||||
"@firebase/installations-types": ["@firebase/installations-types@0.5.3", "", { "peerDependencies": { "@firebase/app-types": "0.x" } }, "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA=="],
|
|
||||||
|
|
||||||
"@firebase/logger": ["@firebase/logger@0.5.0", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g=="],
|
"@firebase/logger": ["@firebase/logger@0.5.0", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g=="],
|
||||||
|
|
||||||
"@firebase/messaging": ["@firebase/messaging@0.12.23", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/installations": "0.6.19", "@firebase/messaging-interop-types": "0.2.3", "@firebase/util": "1.13.0", "idb": "7.1.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-cfuzv47XxqW4HH/OcR5rM+AlQd1xL/VhuaeW/wzMW1LFrsFcTn0GND/hak1vkQc2th8UisBcrkVcQAnOnKwYxg=="],
|
|
||||||
|
|
||||||
"@firebase/messaging-compat": ["@firebase/messaging-compat@0.2.23", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/messaging": "0.12.23", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-SN857v/kBUvlQ9X/UjAqBoQ2FEaL1ZozpnmL1ByTe57iXkmnVVFm9KqAsTfmf+OEwWI4kJJe9NObtN/w22lUgg=="],
|
|
||||||
|
|
||||||
"@firebase/messaging-interop-types": ["@firebase/messaging-interop-types@0.2.3", "", {}, "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q=="],
|
|
||||||
|
|
||||||
"@firebase/performance": ["@firebase/performance@0.7.9", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/installations": "0.6.19", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0", "web-vitals": "^4.2.4" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-UzybENl1EdM2I1sjYm74xGt/0JzRnU/0VmfMAKo2LSpHJzaj77FCLZXmYQ4oOuE+Pxtt8Wy2BVJEENiZkaZAzQ=="],
|
|
||||||
|
|
||||||
"@firebase/performance-compat": ["@firebase/performance-compat@0.2.22", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/performance": "0.7.9", "@firebase/performance-types": "0.2.3", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-xLKxaSAl/FVi10wDX/CHIYEUP13jXUjinL+UaNXT9ByIvxII5Ne5150mx6IgM8G6Q3V+sPiw9C8/kygkyHUVxg=="],
|
|
||||||
|
|
||||||
"@firebase/performance-types": ["@firebase/performance-types@0.2.3", "", {}, "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ=="],
|
|
||||||
|
|
||||||
"@firebase/remote-config": ["@firebase/remote-config@0.8.0", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/installations": "0.6.19", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-sJz7C2VACeE257Z/3kY9Ap2WXbFsgsDLfaGfZmmToKAK39ipXxFan+vzB9CSbF6mP7bzjyzEnqPcMXhAnYE6fQ=="],
|
|
||||||
|
|
||||||
"@firebase/remote-config-compat": ["@firebase/remote-config-compat@0.2.21", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/remote-config": "0.8.0", "@firebase/remote-config-types": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-9+lm0eUycxbu8GO25JfJe4s6R2xlDqlVt0CR6CvN9E6B4AFArEV4qfLoDVRgIEB7nHKwvH2nYRocPWfmjRQTnw=="],
|
|
||||||
|
|
||||||
"@firebase/remote-config-types": ["@firebase/remote-config-types@0.5.0", "", {}, "sha512-vI3bqLoF14L/GchtgayMiFpZJF+Ao3uR8WCde0XpYNkSokDpAKca2DxvcfeZv7lZUqkUwQPL2wD83d3vQ4vvrg=="],
|
|
||||||
|
|
||||||
"@firebase/storage": ["@firebase/storage@0.14.0", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-xWWbb15o6/pWEw8H01UQ1dC5U3rf8QTAzOChYyCpafV6Xki7KVp3Yaw2nSklUwHEziSWE9KoZJS7iYeyqWnYFA=="],
|
|
||||||
|
|
||||||
"@firebase/storage-compat": ["@firebase/storage-compat@0.4.0", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/storage": "0.14.0", "@firebase/storage-types": "0.8.3", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-vDzhgGczr1OfcOy285YAPur5pWDEvD67w4thyeCUh6Ys0izN9fNYtA1MJERmNBfqjqu0lg0FM5GLbw0Il21M+g=="],
|
|
||||||
|
|
||||||
"@firebase/storage-types": ["@firebase/storage-types@0.8.3", "", { "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg=="],
|
|
||||||
|
|
||||||
"@firebase/util": ["@firebase/util@1.13.0", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ=="],
|
"@firebase/util": ["@firebase/util@1.13.0", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ=="],
|
||||||
|
|
||||||
"@firebase/webchannel-wrapper": ["@firebase/webchannel-wrapper@1.0.5", "", {}, "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw=="],
|
|
||||||
|
|
||||||
"@google-cloud/firestore": ["@google-cloud/firestore@7.11.6", "", { "dependencies": { "@opentelemetry/api": "^1.3.0", "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", "google-gax": "^4.3.3", "protobufjs": "^7.2.6" } }, "sha512-EW/O8ktzwLfyWBOsNuhRoMi8lrC3clHM5LVFhGvO1HCsLozCOOXRAlHrYBoE6HL42Sc8yYMuCb2XqcnJ4OOEpw=="],
|
"@google-cloud/firestore": ["@google-cloud/firestore@7.11.6", "", { "dependencies": { "@opentelemetry/api": "^1.3.0", "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", "google-gax": "^4.3.3", "protobufjs": "^7.2.6" } }, "sha512-EW/O8ktzwLfyWBOsNuhRoMi8lrC3clHM5LVFhGvO1HCsLozCOOXRAlHrYBoE6HL42Sc8yYMuCb2XqcnJ4OOEpw=="],
|
||||||
|
|
||||||
"@google-cloud/paginator": ["@google-cloud/paginator@5.0.2", "", { "dependencies": { "arrify": "^2.0.0", "extend": "^3.0.2" } }, "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg=="],
|
"@google-cloud/paginator": ["@google-cloud/paginator@5.0.2", "", { "dependencies": { "arrify": "^2.0.0", "extend": "^3.0.2" } }, "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg=="],
|
||||||
@@ -356,7 +285,7 @@
|
|||||||
|
|
||||||
"@google-cloud/storage": ["@google-cloud/storage@7.18.0", "", { "dependencies": { "@google-cloud/paginator": "^5.0.0", "@google-cloud/projectify": "^4.0.0", "@google-cloud/promisify": "<4.1.0", "abort-controller": "^3.0.0", "async-retry": "^1.3.3", "duplexify": "^4.1.3", "fast-xml-parser": "^4.4.1", "gaxios": "^6.0.2", "google-auth-library": "^9.6.3", "html-entities": "^2.5.2", "mime": "^3.0.0", "p-limit": "^3.0.1", "retry-request": "^7.0.0", "teeny-request": "^9.0.0", "uuid": "^8.0.0" } }, "sha512-r3ZwDMiz4nwW6R922Z1pwpePxyRwE5GdevYX63hRmAQUkUQJcBH/79EnQPDv5cOv1mFBgevdNWQfi3tie3dHrQ=="],
|
"@google-cloud/storage": ["@google-cloud/storage@7.18.0", "", { "dependencies": { "@google-cloud/paginator": "^5.0.0", "@google-cloud/projectify": "^4.0.0", "@google-cloud/promisify": "<4.1.0", "abort-controller": "^3.0.0", "async-retry": "^1.3.3", "duplexify": "^4.1.3", "fast-xml-parser": "^4.4.1", "gaxios": "^6.0.2", "google-auth-library": "^9.6.3", "html-entities": "^2.5.2", "mime": "^3.0.0", "p-limit": "^3.0.1", "retry-request": "^7.0.0", "teeny-request": "^9.0.0", "uuid": "^8.0.0" } }, "sha512-r3ZwDMiz4nwW6R922Z1pwpePxyRwE5GdevYX63hRmAQUkUQJcBH/79EnQPDv5cOv1mFBgevdNWQfi3tie3dHrQ=="],
|
||||||
|
|
||||||
"@grpc/grpc-js": ["@grpc/grpc-js@1.9.15", "", { "dependencies": { "@grpc/proto-loader": "^0.7.8", "@types/node": ">=12.12.47" } }, "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ=="],
|
"@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="],
|
||||||
|
|
||||||
"@grpc/proto-loader": ["@grpc/proto-loader@0.7.15", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ=="],
|
"@grpc/proto-loader": ["@grpc/proto-loader@0.7.15", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ=="],
|
||||||
|
|
||||||
@@ -896,8 +825,6 @@
|
|||||||
|
|
||||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||||
|
|
||||||
"firebase": ["firebase@12.8.0", "", { "dependencies": { "@firebase/ai": "2.7.0", "@firebase/analytics": "0.10.19", "@firebase/analytics-compat": "0.2.25", "@firebase/app": "0.14.7", "@firebase/app-check": "0.11.0", "@firebase/app-check-compat": "0.4.0", "@firebase/app-compat": "0.5.7", "@firebase/app-types": "0.9.3", "@firebase/auth": "1.12.0", "@firebase/auth-compat": "0.6.2", "@firebase/data-connect": "0.3.12", "@firebase/database": "1.1.0", "@firebase/database-compat": "2.1.0", "@firebase/firestore": "4.10.0", "@firebase/firestore-compat": "0.4.4", "@firebase/functions": "0.13.1", "@firebase/functions-compat": "0.4.1", "@firebase/installations": "0.6.19", "@firebase/installations-compat": "0.2.19", "@firebase/messaging": "0.12.23", "@firebase/messaging-compat": "0.2.23", "@firebase/performance": "0.7.9", "@firebase/performance-compat": "0.2.22", "@firebase/remote-config": "0.8.0", "@firebase/remote-config-compat": "0.2.21", "@firebase/storage": "0.14.0", "@firebase/storage-compat": "0.4.0", "@firebase/util": "1.13.0" } }, "sha512-S1tCIR3ENecee0tY2cfTHfMkXqkitHfbsvqpCtvsT0Zi9vDB7A4CodAjHfHCjVvu/XtGy1LHLjOasVcF10rCVw=="],
|
|
||||||
|
|
||||||
"firebase-admin": ["firebase-admin@13.6.0", "", { "dependencies": { "@fastify/busboy": "^3.0.0", "@firebase/database-compat": "^2.0.0", "@firebase/database-types": "^1.0.6", "@types/node": "^22.8.7", "farmhash-modern": "^1.1.0", "fast-deep-equal": "^3.1.1", "google-auth-library": "^9.14.2", "jsonwebtoken": "^9.0.0", "jwks-rsa": "^3.1.0", "node-forge": "^1.3.1", "uuid": "^11.0.2" }, "optionalDependencies": { "@google-cloud/firestore": "^7.11.0", "@google-cloud/storage": "^7.14.0" } }, "sha512-GdPA/t0+Cq8p1JnjFRBmxRxAGvF/kl2yfdhALl38PrRp325YxyQ5aNaHui0XmaKcKiGRFIJ/EgBNWFoDP0onjw=="],
|
"firebase-admin": ["firebase-admin@13.6.0", "", { "dependencies": { "@fastify/busboy": "^3.0.0", "@firebase/database-compat": "^2.0.0", "@firebase/database-types": "^1.0.6", "@types/node": "^22.8.7", "farmhash-modern": "^1.1.0", "fast-deep-equal": "^3.1.1", "google-auth-library": "^9.14.2", "jsonwebtoken": "^9.0.0", "jwks-rsa": "^3.1.0", "node-forge": "^1.3.1", "uuid": "^11.0.2" }, "optionalDependencies": { "@google-cloud/firestore": "^7.11.0", "@google-cloud/storage": "^7.14.0" } }, "sha512-GdPA/t0+Cq8p1JnjFRBmxRxAGvF/kl2yfdhALl38PrRp325YxyQ5aNaHui0XmaKcKiGRFIJ/EgBNWFoDP0onjw=="],
|
||||||
|
|
||||||
"form-data": ["form-data@2.5.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" } }, "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A=="],
|
"form-data": ["form-data@2.5.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" } }, "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A=="],
|
||||||
@@ -952,8 +879,6 @@
|
|||||||
|
|
||||||
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
||||||
|
|
||||||
"idb": ["idb@7.1.1", "", {}, "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="],
|
|
||||||
|
|
||||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||||
|
|
||||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||||
@@ -1184,8 +1109,6 @@
|
|||||||
|
|
||||||
"vue-router": ["vue-router@4.6.4", "", { "dependencies": { "@vue/devtools-api": "^6.6.4" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg=="],
|
"vue-router": ["vue-router@4.6.4", "", { "dependencies": { "@vue/devtools-api": "^6.6.4" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg=="],
|
||||||
|
|
||||||
"web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="],
|
|
||||||
|
|
||||||
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||||
|
|
||||||
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
||||||
@@ -1234,6 +1157,8 @@
|
|||||||
|
|
||||||
"@google-cloud/storage/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
|
"@google-cloud/storage/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
|
||||||
|
|
||||||
|
"@grpc/grpc-js/@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="],
|
||||||
|
|
||||||
"@jridgewell/gen-mapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
"@jridgewell/gen-mapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
"@jridgewell/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
"@jridgewell/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
@@ -1262,8 +1187,6 @@
|
|||||||
|
|
||||||
"gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
"gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||||
|
|
||||||
"google-gax/@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="],
|
|
||||||
|
|
||||||
"google-gax/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
"google-gax/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||||
|
|
||||||
"http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
|
"http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
|
||||||
@@ -1308,8 +1231,6 @@
|
|||||||
|
|
||||||
"firebase-admin/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
"firebase-admin/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||||
|
|
||||||
"google-gax/@grpc/grpc-js/@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="],
|
|
||||||
|
|
||||||
"lru-memoizer/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
|
"lru-memoizer/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
|
||||||
|
|
||||||
"mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
"mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
||||||
|
|||||||
518
docs.json
Normal file
518
docs.json
Normal file
@@ -0,0 +1,518 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"description": "This is the API server for Stream application.",
|
||||||
|
"title": "Stream API",
|
||||||
|
"termsOfService": "http://swagger.io/terms/",
|
||||||
|
"contact": {
|
||||||
|
"name": "API Support",
|
||||||
|
"url": "http://www.swagger.io/support",
|
||||||
|
"email": "support@swagger.io"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "Apache 2.0",
|
||||||
|
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||||
|
},
|
||||||
|
"version": "1.0"
|
||||||
|
},
|
||||||
|
"host": "localhost:8080",
|
||||||
|
"basePath": "/",
|
||||||
|
"paths": {
|
||||||
|
"/payments": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Create a new payment",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"payment"
|
||||||
|
],
|
||||||
|
"summary": "Create Payment",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Payment Info",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/payment.CreatePaymentRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/plans": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Get all active plans",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"plan"
|
||||||
|
],
|
||||||
|
"summary": "List Plans",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/model.Plan"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/videos": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Get paginated videos",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"video"
|
||||||
|
],
|
||||||
|
"summary": "List Videos",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1,
|
||||||
|
"description": "Page number",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"default": 10,
|
||||||
|
"description": "Page size",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Create video record after upload",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"video"
|
||||||
|
],
|
||||||
|
"summary": "Create Video",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Video Info",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/video.CreateVideoRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/model.Video"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/videos/upload-url": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Generate presigned URL for video upload",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"video"
|
||||||
|
],
|
||||||
|
"summary": "Get Upload URL",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "File Info",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/video.UploadURLRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/videos/{id}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Get video details by ID",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"video"
|
||||||
|
],
|
||||||
|
"summary": "Get Video",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Video ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/model.Video"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"model.Plan": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"cycle": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"duration_limit": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"is_active": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"quality_limit": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"storage_limit": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"upload_limit": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model.Video": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"created_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"hls_path": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"hls_token": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"processing_status": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"storage_type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"thumbnail": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"views": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"payment.CreatePaymentRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"amount",
|
||||||
|
"plan_id"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"amount": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"plan_id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response.Response": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"data": {},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"video.CreateVideoRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"size",
|
||||||
|
"title",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"description": "Maybe client knows, or we process later",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"description": "The S3 Key or Full URL",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"video.UploadURLRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"content_type",
|
||||||
|
"filename",
|
||||||
|
"size"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"content_type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"filename": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securityDefinitions": {
|
||||||
|
"BearerAuth": {
|
||||||
|
"type": "apiKey",
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
"@unhead/vue": "^2.1.1",
|
"@unhead/vue": "^2.1.1",
|
||||||
"@vueuse/core": "^14.1.0",
|
"@vueuse/core": "^14.1.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"firebase": "^12.8.0",
|
|
||||||
"firebase-admin": "^13.6.0",
|
"firebase-admin": "^13.6.0",
|
||||||
"hono": "^4.11.3",
|
"hono": "^4.11.3",
|
||||||
"is-mobile": "^5.0.0",
|
"is-mobile": "^5.0.0",
|
||||||
|
|||||||
678
src/api/client.ts
Normal file
678
src/api/client.ts
Normal file
@@ -0,0 +1,678 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* tslint:disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* ---------------------------------------------------------------
|
||||||
|
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||||
|
* ## ##
|
||||||
|
* ## AUTHOR: acacode ##
|
||||||
|
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
||||||
|
* ---------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
import { customFetch } from "@httpClientAdapter";
|
||||||
|
export interface AuthForgotPasswordRequest {
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthLoginRequest {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthRegisterRequest {
|
||||||
|
email: string;
|
||||||
|
/** @minLength 6 */
|
||||||
|
password: string;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthResetPasswordRequest {
|
||||||
|
/** @minLength 6 */
|
||||||
|
new_password: string;
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModelPlan {
|
||||||
|
cycle?: string;
|
||||||
|
description?: string;
|
||||||
|
duration_limit?: number;
|
||||||
|
features?: string;
|
||||||
|
id?: string;
|
||||||
|
is_active?: boolean;
|
||||||
|
name?: string;
|
||||||
|
price?: number;
|
||||||
|
quality_limit?: string;
|
||||||
|
storage_limit?: number;
|
||||||
|
upload_limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModelUser {
|
||||||
|
avatar?: string;
|
||||||
|
created_at?: string;
|
||||||
|
email?: string;
|
||||||
|
google_id?: string;
|
||||||
|
id?: string;
|
||||||
|
password?: string;
|
||||||
|
plan_id?: string;
|
||||||
|
role?: string;
|
||||||
|
storage_used?: number;
|
||||||
|
updated_at?: string;
|
||||||
|
username?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModelVideo {
|
||||||
|
created_at?: string;
|
||||||
|
description?: string;
|
||||||
|
duration?: number;
|
||||||
|
format?: string;
|
||||||
|
hls_path?: string;
|
||||||
|
hls_token?: string;
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
processing_status?: string;
|
||||||
|
size?: number;
|
||||||
|
status?: string;
|
||||||
|
storage_type?: string;
|
||||||
|
thumbnail?: string;
|
||||||
|
title?: string;
|
||||||
|
updated_at?: string;
|
||||||
|
url?: string;
|
||||||
|
user_id?: string;
|
||||||
|
views?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaymentCreatePaymentRequest {
|
||||||
|
amount: number;
|
||||||
|
plan_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResponseResponse {
|
||||||
|
code?: number;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VideoCreateVideoRequest {
|
||||||
|
description?: string;
|
||||||
|
/** Maybe client knows, or we process later */
|
||||||
|
duration?: number;
|
||||||
|
format?: string;
|
||||||
|
size: number;
|
||||||
|
title: string;
|
||||||
|
/** The S3 Key or Full URL */
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VideoUploadURLRequest {
|
||||||
|
content_type: string;
|
||||||
|
filename: string;
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type QueryParamsType = Record<string | number, any>;
|
||||||
|
export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
|
||||||
|
|
||||||
|
export interface FullRequestParams extends Omit<RequestInit, "body"> {
|
||||||
|
/** set parameter to `true` for call `securityWorker` for this request */
|
||||||
|
secure?: boolean;
|
||||||
|
/** request path */
|
||||||
|
path: string;
|
||||||
|
/** content type of request body */
|
||||||
|
type?: ContentType;
|
||||||
|
/** query params */
|
||||||
|
query?: QueryParamsType;
|
||||||
|
/** format of response (i.e. response.json() -> format: "json") */
|
||||||
|
format?: ResponseFormat;
|
||||||
|
/** request body */
|
||||||
|
body?: unknown;
|
||||||
|
/** base url */
|
||||||
|
baseUrl?: string;
|
||||||
|
/** request cancellation token */
|
||||||
|
cancelToken?: CancelToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RequestParams = Omit<
|
||||||
|
FullRequestParams,
|
||||||
|
"body" | "method" | "query" | "path"
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface ApiConfig<SecurityDataType = unknown> {
|
||||||
|
baseUrl?: string;
|
||||||
|
baseApiParams?: Omit<RequestParams, "baseUrl" | "cancelToken" | "signal">;
|
||||||
|
securityWorker?: (
|
||||||
|
securityData: SecurityDataType | null,
|
||||||
|
) => Promise<RequestParams | void> | RequestParams | void;
|
||||||
|
customFetch?: typeof fetch;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HttpResponse<D extends unknown, E extends unknown = unknown>
|
||||||
|
extends Response {
|
||||||
|
data: D;
|
||||||
|
error: E;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CancelToken = Symbol | string | number;
|
||||||
|
|
||||||
|
export enum ContentType {
|
||||||
|
Json = "application/json",
|
||||||
|
JsonApi = "application/vnd.api+json",
|
||||||
|
FormData = "multipart/form-data",
|
||||||
|
UrlEncoded = "application/x-www-form-urlencoded",
|
||||||
|
Text = "text/plain",
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HttpClient<SecurityDataType = unknown> {
|
||||||
|
public baseUrl: string = "";
|
||||||
|
private securityData: SecurityDataType | null = null;
|
||||||
|
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
|
||||||
|
private abortControllers = new Map<CancelToken, AbortController>();
|
||||||
|
private customFetch = (...fetchParams: Parameters<typeof fetch>) =>
|
||||||
|
fetch(...fetchParams);
|
||||||
|
|
||||||
|
private baseApiParams: RequestParams = {
|
||||||
|
credentials: "same-origin",
|
||||||
|
headers: {},
|
||||||
|
redirect: "follow",
|
||||||
|
referrerPolicy: "no-referrer",
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(apiConfig: ApiConfig<SecurityDataType> = {}) {
|
||||||
|
Object.assign(this, apiConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setSecurityData = (data: SecurityDataType | null) => {
|
||||||
|
this.securityData = data;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected encodeQueryParam(key: string, value: any) {
|
||||||
|
const encodedKey = encodeURIComponent(key);
|
||||||
|
return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected addQueryParam(query: QueryParamsType, key: string) {
|
||||||
|
return this.encodeQueryParam(key, query[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected addArrayQueryParam(query: QueryParamsType, key: string) {
|
||||||
|
const value = query[key];
|
||||||
|
return value.map((v: any) => this.encodeQueryParam(key, v)).join("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toQueryString(rawQuery?: QueryParamsType): string {
|
||||||
|
const query = rawQuery || {};
|
||||||
|
const keys = Object.keys(query).filter(
|
||||||
|
(key) => "undefined" !== typeof query[key],
|
||||||
|
);
|
||||||
|
return keys
|
||||||
|
.map((key) =>
|
||||||
|
Array.isArray(query[key])
|
||||||
|
? this.addArrayQueryParam(query, key)
|
||||||
|
: this.addQueryParam(query, key),
|
||||||
|
)
|
||||||
|
.join("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected addQueryParams(rawQuery?: QueryParamsType): string {
|
||||||
|
const queryString = this.toQueryString(rawQuery);
|
||||||
|
return queryString ? `?${queryString}` : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private contentFormatters: Record<ContentType, (input: any) => any> = {
|
||||||
|
[ContentType.Json]: (input: any) =>
|
||||||
|
input !== null && (typeof input === "object" || typeof input === "string")
|
||||||
|
? JSON.stringify(input)
|
||||||
|
: input,
|
||||||
|
[ContentType.JsonApi]: (input: any) =>
|
||||||
|
input !== null && (typeof input === "object" || typeof input === "string")
|
||||||
|
? JSON.stringify(input)
|
||||||
|
: input,
|
||||||
|
[ContentType.Text]: (input: any) =>
|
||||||
|
input !== null && typeof input !== "string"
|
||||||
|
? JSON.stringify(input)
|
||||||
|
: input,
|
||||||
|
[ContentType.FormData]: (input: any) => {
|
||||||
|
if (input instanceof FormData) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(input || {}).reduce((formData, key) => {
|
||||||
|
const property = input[key];
|
||||||
|
formData.append(
|
||||||
|
key,
|
||||||
|
property instanceof Blob
|
||||||
|
? property
|
||||||
|
: typeof property === "object" && property !== null
|
||||||
|
? JSON.stringify(property)
|
||||||
|
: `${property}`,
|
||||||
|
);
|
||||||
|
return formData;
|
||||||
|
}, new FormData());
|
||||||
|
},
|
||||||
|
[ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
|
||||||
|
};
|
||||||
|
|
||||||
|
protected mergeRequestParams(
|
||||||
|
params1: RequestParams,
|
||||||
|
params2?: RequestParams,
|
||||||
|
): RequestParams {
|
||||||
|
return {
|
||||||
|
...this.baseApiParams,
|
||||||
|
...params1,
|
||||||
|
...(params2 || {}),
|
||||||
|
headers: {
|
||||||
|
...(this.baseApiParams.headers || {}),
|
||||||
|
...(params1.headers || {}),
|
||||||
|
...((params2 && params2.headers) || {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createAbortSignal = (
|
||||||
|
cancelToken: CancelToken,
|
||||||
|
): AbortSignal | undefined => {
|
||||||
|
if (this.abortControllers.has(cancelToken)) {
|
||||||
|
const abortController = this.abortControllers.get(cancelToken);
|
||||||
|
if (abortController) {
|
||||||
|
return abortController.signal;
|
||||||
|
}
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const abortController = new AbortController();
|
||||||
|
this.abortControllers.set(cancelToken, abortController);
|
||||||
|
return abortController.signal;
|
||||||
|
};
|
||||||
|
|
||||||
|
public abortRequest = (cancelToken: CancelToken) => {
|
||||||
|
const abortController = this.abortControllers.get(cancelToken);
|
||||||
|
|
||||||
|
if (abortController) {
|
||||||
|
abortController.abort();
|
||||||
|
this.abortControllers.delete(cancelToken);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public request = async <T = any, E = any>({
|
||||||
|
body,
|
||||||
|
secure,
|
||||||
|
path,
|
||||||
|
type,
|
||||||
|
query,
|
||||||
|
format,
|
||||||
|
baseUrl,
|
||||||
|
cancelToken,
|
||||||
|
...params
|
||||||
|
}: FullRequestParams): Promise<HttpResponse<T, E>> => {
|
||||||
|
const secureParams =
|
||||||
|
((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
|
||||||
|
this.securityWorker &&
|
||||||
|
(await this.securityWorker(this.securityData))) ||
|
||||||
|
{};
|
||||||
|
const requestParams = this.mergeRequestParams(params, secureParams);
|
||||||
|
const queryString = query && this.toQueryString(query);
|
||||||
|
const payloadFormatter = this.contentFormatters[type || ContentType.Json];
|
||||||
|
const responseFormat = format || requestParams.format;
|
||||||
|
|
||||||
|
return this.customFetch(
|
||||||
|
`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`,
|
||||||
|
{
|
||||||
|
...requestParams,
|
||||||
|
headers: {
|
||||||
|
...(requestParams.headers || {}),
|
||||||
|
...(type && type !== ContentType.FormData
|
||||||
|
? { "Content-Type": type }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
signal:
|
||||||
|
(cancelToken
|
||||||
|
? this.createAbortSignal(cancelToken)
|
||||||
|
: requestParams.signal) || null,
|
||||||
|
body:
|
||||||
|
typeof body === "undefined" || body === null
|
||||||
|
? null
|
||||||
|
: payloadFormatter(body)
|
||||||
|
},
|
||||||
|
).then(async (response) => {
|
||||||
|
const r = response as HttpResponse<T, E>;
|
||||||
|
r.data = null as unknown as T;
|
||||||
|
r.error = null as unknown as E;
|
||||||
|
|
||||||
|
const responseToParse = responseFormat ? response.clone() : response;
|
||||||
|
const data = !responseFormat
|
||||||
|
? r
|
||||||
|
: await responseToParse[responseFormat]()
|
||||||
|
.then((data) => {
|
||||||
|
if (r.ok) {
|
||||||
|
r.data = data;
|
||||||
|
} else {
|
||||||
|
r.error = data;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
r.error = e;
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cancelToken) {
|
||||||
|
this.abortControllers.delete(cancelToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) throw data;
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title Stream API
|
||||||
|
* @version 1.0
|
||||||
|
* @license Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||||
|
* @termsOfService http://swagger.io/terms/
|
||||||
|
* @contact API Support <support@swagger.io> (http://www.swagger.io/support)
|
||||||
|
*
|
||||||
|
* This is the API server for Stream application.
|
||||||
|
*/
|
||||||
|
export class Api<
|
||||||
|
SecurityDataType extends unknown,
|
||||||
|
> extends HttpClient<SecurityDataType> {
|
||||||
|
auth = {
|
||||||
|
/**
|
||||||
|
* @description Request password reset link
|
||||||
|
*
|
||||||
|
* @tags auth
|
||||||
|
* @name ForgotPasswordCreate
|
||||||
|
* @summary Forgot Password
|
||||||
|
* @request POST:/auth/forgot-password
|
||||||
|
*/
|
||||||
|
forgotPasswordCreate: (
|
||||||
|
request: AuthForgotPasswordRequest,
|
||||||
|
params: RequestParams = {},
|
||||||
|
) =>
|
||||||
|
this.request<ResponseResponse, ResponseResponse>({
|
||||||
|
path: `/auth/forgot-password`,
|
||||||
|
method: "POST",
|
||||||
|
body: request,
|
||||||
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Callback for Google Login
|
||||||
|
*
|
||||||
|
* @tags auth
|
||||||
|
* @name GoogleCallbackList
|
||||||
|
* @summary Google Callback
|
||||||
|
* @request GET:/auth/google/callback
|
||||||
|
*/
|
||||||
|
googleCallbackList: (params: RequestParams = {}) =>
|
||||||
|
this.request<
|
||||||
|
ResponseResponse & {
|
||||||
|
data?: ModelUser;
|
||||||
|
},
|
||||||
|
ResponseResponse
|
||||||
|
>({
|
||||||
|
path: `/auth/google/callback`,
|
||||||
|
method: "GET",
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Redirect to Google for Login
|
||||||
|
*
|
||||||
|
* @tags auth
|
||||||
|
* @name GoogleLoginList
|
||||||
|
* @summary Google Login
|
||||||
|
* @request GET:/auth/google/login
|
||||||
|
*/
|
||||||
|
googleLoginList: (params: RequestParams = {}) =>
|
||||||
|
this.request<any, void>({
|
||||||
|
path: `/auth/google/login`,
|
||||||
|
method: "GET",
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Login with email and password
|
||||||
|
*
|
||||||
|
* @tags auth
|
||||||
|
* @name LoginCreate
|
||||||
|
* @summary Login
|
||||||
|
* @request POST:/auth/login
|
||||||
|
*/
|
||||||
|
loginCreate: (request: AuthLoginRequest, params: RequestParams = {}) =>
|
||||||
|
this.request<
|
||||||
|
ResponseResponse & {
|
||||||
|
data?: ModelUser;
|
||||||
|
},
|
||||||
|
ResponseResponse
|
||||||
|
>({
|
||||||
|
path: `/auth/login`,
|
||||||
|
method: "POST",
|
||||||
|
body: request,
|
||||||
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Logout user and clear cookies
|
||||||
|
*
|
||||||
|
* @tags auth
|
||||||
|
* @name LogoutCreate
|
||||||
|
* @summary Logout
|
||||||
|
* @request POST:/auth/logout
|
||||||
|
*/
|
||||||
|
logoutCreate: (params: RequestParams = {}) =>
|
||||||
|
this.request<ResponseResponse, any>({
|
||||||
|
path: `/auth/logout`,
|
||||||
|
method: "POST",
|
||||||
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Register a new user
|
||||||
|
*
|
||||||
|
* @tags auth
|
||||||
|
* @name RegisterCreate
|
||||||
|
* @summary Register
|
||||||
|
* @request POST:/auth/register
|
||||||
|
*/
|
||||||
|
registerCreate: (
|
||||||
|
request: AuthRegisterRequest,
|
||||||
|
params: RequestParams = {},
|
||||||
|
) =>
|
||||||
|
this.request<ResponseResponse, ResponseResponse>({
|
||||||
|
path: `/auth/register`,
|
||||||
|
method: "POST",
|
||||||
|
body: request,
|
||||||
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Reset password using token
|
||||||
|
*
|
||||||
|
* @tags auth
|
||||||
|
* @name ResetPasswordCreate
|
||||||
|
* @summary Reset Password
|
||||||
|
* @request POST:/auth/reset-password
|
||||||
|
*/
|
||||||
|
resetPasswordCreate: (
|
||||||
|
request: AuthResetPasswordRequest,
|
||||||
|
params: RequestParams = {},
|
||||||
|
) =>
|
||||||
|
this.request<ResponseResponse, ResponseResponse>({
|
||||||
|
path: `/auth/reset-password`,
|
||||||
|
method: "POST",
|
||||||
|
body: request,
|
||||||
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
payments = {
|
||||||
|
/**
|
||||||
|
* @description Create a new payment
|
||||||
|
*
|
||||||
|
* @tags payment
|
||||||
|
* @name PaymentsCreate
|
||||||
|
* @summary Create Payment
|
||||||
|
* @request POST:/payments
|
||||||
|
* @secure
|
||||||
|
*/
|
||||||
|
paymentsCreate: (
|
||||||
|
request: PaymentCreatePaymentRequest,
|
||||||
|
params: RequestParams = {},
|
||||||
|
) =>
|
||||||
|
this.request<ResponseResponse, ResponseResponse>({
|
||||||
|
path: `/payments`,
|
||||||
|
method: "POST",
|
||||||
|
body: request,
|
||||||
|
secure: true,
|
||||||
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
plans = {
|
||||||
|
/**
|
||||||
|
* @description Get all active plans
|
||||||
|
*
|
||||||
|
* @tags plan
|
||||||
|
* @name PlansList
|
||||||
|
* @summary List Plans
|
||||||
|
* @request GET:/plans
|
||||||
|
* @secure
|
||||||
|
*/
|
||||||
|
plansList: (params: RequestParams = {}) =>
|
||||||
|
this.request<
|
||||||
|
ResponseResponse & {
|
||||||
|
data?: ModelPlan[];
|
||||||
|
},
|
||||||
|
ResponseResponse
|
||||||
|
>({
|
||||||
|
path: `/plans`,
|
||||||
|
method: "GET",
|
||||||
|
secure: true,
|
||||||
|
format: "json",
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
videos = {
|
||||||
|
/**
|
||||||
|
* @description Get paginated videos
|
||||||
|
*
|
||||||
|
* @tags video
|
||||||
|
* @name VideosList
|
||||||
|
* @summary List Videos
|
||||||
|
* @request GET:/videos
|
||||||
|
* @secure
|
||||||
|
*/
|
||||||
|
videosList: (
|
||||||
|
query?: {
|
||||||
|
/**
|
||||||
|
* Page number
|
||||||
|
* @default 1
|
||||||
|
*/
|
||||||
|
page?: number;
|
||||||
|
/**
|
||||||
|
* Page size
|
||||||
|
* @default 10
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
},
|
||||||
|
params: RequestParams = {},
|
||||||
|
) =>
|
||||||
|
this.request<ResponseResponse, ResponseResponse>({
|
||||||
|
path: `/videos`,
|
||||||
|
method: "GET",
|
||||||
|
query: query,
|
||||||
|
secure: true,
|
||||||
|
format: "json",
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Create video record after upload
|
||||||
|
*
|
||||||
|
* @tags video
|
||||||
|
* @name VideosCreate
|
||||||
|
* @summary Create Video
|
||||||
|
* @request POST:/videos
|
||||||
|
* @secure
|
||||||
|
*/
|
||||||
|
videosCreate: (
|
||||||
|
request: VideoCreateVideoRequest,
|
||||||
|
params: RequestParams = {},
|
||||||
|
) =>
|
||||||
|
this.request<
|
||||||
|
ResponseResponse & {
|
||||||
|
data?: ModelVideo;
|
||||||
|
},
|
||||||
|
ResponseResponse
|
||||||
|
>({
|
||||||
|
path: `/videos`,
|
||||||
|
method: "POST",
|
||||||
|
body: request,
|
||||||
|
secure: true,
|
||||||
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Generate presigned URL for video upload
|
||||||
|
*
|
||||||
|
* @tags video
|
||||||
|
* @name UploadUrlCreate
|
||||||
|
* @summary Get Upload URL
|
||||||
|
* @request POST:/videos/upload-url
|
||||||
|
* @secure
|
||||||
|
*/
|
||||||
|
uploadUrlCreate: (
|
||||||
|
request: VideoUploadURLRequest,
|
||||||
|
params: RequestParams = {},
|
||||||
|
) =>
|
||||||
|
this.request<ResponseResponse, ResponseResponse>({
|
||||||
|
path: `/videos/upload-url`,
|
||||||
|
method: "POST",
|
||||||
|
body: request,
|
||||||
|
secure: true,
|
||||||
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Get video details by ID
|
||||||
|
*
|
||||||
|
* @tags video
|
||||||
|
* @name VideosDetail
|
||||||
|
* @summary Get Video
|
||||||
|
* @request GET:/videos/{id}
|
||||||
|
* @secure
|
||||||
|
*/
|
||||||
|
videosDetail: (id: string, params: RequestParams = {}) =>
|
||||||
|
this.request<
|
||||||
|
ResponseResponse & {
|
||||||
|
data?: ModelVideo;
|
||||||
|
},
|
||||||
|
ResponseResponse
|
||||||
|
>({
|
||||||
|
path: `/videos/${id}`,
|
||||||
|
method: "GET",
|
||||||
|
secure: true,
|
||||||
|
format: "json",
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const client = new Api({
|
||||||
|
baseUrl: 'http://localhost:8080',
|
||||||
|
customFetch
|
||||||
|
});
|
||||||
@@ -1,65 +1,6 @@
|
|||||||
import { TinyRpcClientAdapter, TinyRpcError } from "@hiogawa/tiny-rpc";
|
export const customFetch = (url: string, options: RequestInit) => {
|
||||||
import { Result } from "@hiogawa/utils";
|
return fetch(url, {
|
||||||
|
...options,
|
||||||
const GET_PAYLOAD_PARAM = "payload";
|
|
||||||
|
|
||||||
export function httpClientAdapter(opts: {
|
|
||||||
url: string;
|
|
||||||
pathsForGET?: string[];
|
|
||||||
headers?: () => Promise<Record<string, string>> | Record<string, string>;
|
|
||||||
}): TinyRpcClientAdapter {
|
|
||||||
return {
|
|
||||||
send: async (data) => {
|
|
||||||
const url = [opts.url, data.path].join("/");
|
|
||||||
const payload = JSON.stringify(data.args);
|
|
||||||
const method = opts.pathsForGET?.includes(data.path)
|
|
||||||
? "GET"
|
|
||||||
: "POST";
|
|
||||||
|
|
||||||
const extraHeaders = opts.headers ? await opts.headers() : {};
|
|
||||||
|
|
||||||
let req: Request;
|
|
||||||
if (method === "GET") {
|
|
||||||
req = new Request(
|
|
||||||
url +
|
|
||||||
"?" +
|
|
||||||
new URLSearchParams({ [GET_PAYLOAD_PARAM]: payload }),
|
|
||||||
{
|
|
||||||
headers: extraHeaders
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
req = new Request(url, {
|
|
||||||
method: "POST",
|
|
||||||
body: payload,
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json; charset=utf-8",
|
|
||||||
...extraHeaders
|
|
||||||
},
|
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let res: Response;
|
|
||||||
res = await fetch(req);
|
|
||||||
if (!res.ok) {
|
|
||||||
// throw new Error(`HTTP error: ${res.status}`);
|
|
||||||
throw new Error(
|
|
||||||
JSON.stringify({
|
|
||||||
status: res.status,
|
|
||||||
statusText: res.statusText,
|
|
||||||
data: { message: await res.text() },
|
|
||||||
internal: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
// throw TinyRpcError.deserialize(res.status);
|
|
||||||
}
|
|
||||||
const result: Result<unknown, unknown> = JSON.parse(
|
|
||||||
await res.text()
|
|
||||||
);
|
|
||||||
if (!result.ok) {
|
|
||||||
throw TinyRpcError.deserialize(result.value);
|
|
||||||
}
|
|
||||||
return result.value;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,70 +1,19 @@
|
|||||||
import { TinyRpcClientAdapter, TinyRpcError } from "@hiogawa/tiny-rpc";
|
|
||||||
import { Result } from "@hiogawa/utils";
|
|
||||||
import { tryGetContext } from "hono/context-storage";
|
import { tryGetContext } from "hono/context-storage";
|
||||||
|
|
||||||
const GET_PAYLOAD_PARAM = "payload";
|
export const customFetch = async (url: string, options: RequestInit) => {
|
||||||
|
options.credentials = "include";
|
||||||
export function httpClientAdapter(opts: {
|
if (!options.headers) {
|
||||||
url: string;
|
options.headers = {};
|
||||||
pathsForGET?: string[];
|
|
||||||
headers?: () => Promise<Record<string, string>> | Record<string, string>;
|
|
||||||
}): TinyRpcClientAdapter {
|
|
||||||
return {
|
|
||||||
send: async (data) => {
|
|
||||||
const url = [opts.url, data.path].join("/");
|
|
||||||
const payload = JSON.stringify(data.args);
|
|
||||||
const method = opts.pathsForGET?.includes(data.path)
|
|
||||||
? "GET"
|
|
||||||
: "POST";
|
|
||||||
let req: Request;
|
|
||||||
if (method === "GET") {
|
|
||||||
req = new Request(
|
|
||||||
url +
|
|
||||||
"?" +
|
|
||||||
new URLSearchParams({ [GET_PAYLOAD_PARAM]: payload })
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
req = new Request(url, {
|
|
||||||
method: "POST",
|
|
||||||
body: payload,
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json; charset=utf-8",
|
|
||||||
},
|
|
||||||
credentials: "include",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
let res: Response;
|
|
||||||
if (import.meta.env.SSR) {
|
if (import.meta.env.SSR) {
|
||||||
const c = tryGetContext<any>();
|
const c = tryGetContext<any>();
|
||||||
if (!c) {
|
if (!c) {
|
||||||
throw new Error("Hono context not found in SSR");
|
throw new Error("Hono context not found in SSR");
|
||||||
}
|
}
|
||||||
Object.entries(c.req.header()).forEach(([k, v]) => {
|
Object.entries(c.req.header()).forEach(([k, v]) => {
|
||||||
req.headers.append(k, v);
|
Object.assign(options.headers!, { [k]: v });
|
||||||
});
|
});
|
||||||
res = await c.get("fetch")(req);
|
return await c.get("fetch")(url, options);
|
||||||
} else {
|
|
||||||
res = await fetch(req);
|
|
||||||
}
|
}
|
||||||
if (!res.ok) {
|
return fetch(url, options);
|
||||||
// throw new Error(`HTTP error: ${res.status}`);
|
|
||||||
throw new Error(
|
|
||||||
JSON.stringify({
|
|
||||||
status: res.status,
|
|
||||||
statusText: res.statusText,
|
|
||||||
data: { message: await res.text() },
|
|
||||||
internal: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
// throw TinyRpcError.deserialize(res.status);
|
|
||||||
}
|
|
||||||
const result: Result<unknown, unknown> = JSON.parse(
|
|
||||||
await res.text()
|
|
||||||
);
|
|
||||||
if (!result.ok) {
|
|
||||||
throw TinyRpcError.deserialize(result.value);
|
|
||||||
}
|
|
||||||
return result.value;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import {
|
|
||||||
proxyTinyRpc,
|
|
||||||
TinyRpcClientAdapter,
|
|
||||||
TinyRpcError,
|
|
||||||
} from "@hiogawa/tiny-rpc";
|
|
||||||
import type { RpcRoutes } from "./rpc";
|
|
||||||
import { Result } from "@hiogawa/utils";
|
|
||||||
import { httpClientAdapter } from "@httpClientAdapter";
|
|
||||||
// console.log("httpClientAdapter module:", httpClientAdapter.toString());
|
|
||||||
declare let __host__: string;
|
|
||||||
const endpoint = "/rpc";
|
|
||||||
const url = import.meta.env.SSR ? "http://localhost" : "";
|
|
||||||
import { auth } from "../lib/firebase";
|
|
||||||
|
|
||||||
export const client = proxyTinyRpc<RpcRoutes>({
|
|
||||||
adapter: httpClientAdapter({
|
|
||||||
url: url + endpoint,
|
|
||||||
pathsForGET: [],
|
|
||||||
headers: async () => {
|
|
||||||
if (import.meta.env.SSR) return {}; // No client auth on server for now
|
|
||||||
const user = auth.currentUser;
|
|
||||||
if (user) {
|
|
||||||
// Force refresh if needed or just get token
|
|
||||||
const token = await user.getIdToken();
|
|
||||||
return { Authorization: `Bearer ${token}` };
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { initializeApp } from "firebase/app";
|
|
||||||
import { createUserWithEmailAndPassword, getAuth, GoogleAuthProvider, sendPasswordResetEmail, signInWithEmailAndPassword, signInWithPopup } from "firebase/auth";
|
|
||||||
// TODO: Add SDKs for Firebase products that you want to use
|
|
||||||
// https://firebase.google.com/docs/web/setup#available-libraries
|
|
||||||
|
|
||||||
// Your web app's Firebase configuration
|
|
||||||
const firebaseConfig = {
|
|
||||||
apiKey: "AIzaSyBTr0L5qxdrVEtWuP2oAicJXQvVyeXkMts",
|
|
||||||
authDomain: "trello-7ea39.firebaseapp.com",
|
|
||||||
projectId: "trello-7ea39",
|
|
||||||
storageBucket: "trello-7ea39.firebasestorage.app",
|
|
||||||
messagingSenderId: "321067890572",
|
|
||||||
appId: "1:321067890572:web:e34e1e657125d37be688a9"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize Firebase
|
|
||||||
const appFirebase = initializeApp(firebaseConfig);
|
|
||||||
const provider = new GoogleAuthProvider();
|
|
||||||
export const auth = getAuth(appFirebase);
|
|
||||||
export const googleAuth = () => signInWithPopup(auth, provider).then((result) => {
|
|
||||||
console.log('User signed in:', result.user);
|
|
||||||
return result;
|
|
||||||
})
|
|
||||||
export const emailAuth = (username: string, password: string) => {
|
|
||||||
return signInWithEmailAndPassword(auth, username, password)
|
|
||||||
}
|
|
||||||
export const forgotPassword = (email: string) => {
|
|
||||||
return sendPasswordResetEmail(auth, email)
|
|
||||||
.then(() => {
|
|
||||||
console.log('Password reset email sent');
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error sending password reset email:', error);
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
export const signUp = (email: string, password: string) => {
|
|
||||||
return createUserWithEmailAndPassword(auth, email, password)
|
|
||||||
.then((userCredential) => {
|
|
||||||
console.log('User signed up:', userCredential.user);
|
|
||||||
return userCredential.user;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error signing up:', error);
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
// import { initializeApp, getApps, cert } from 'firebase-admin/app';
|
|
||||||
// import { getAuth } from 'firebase-admin/auth';
|
|
||||||
// import certJson from './cert.json';
|
|
||||||
// const firebaseAdminConfig = {
|
|
||||||
// credential: cert(certJson as any)
|
|
||||||
// };
|
|
||||||
|
|
||||||
// if (getApps().length === 0) {
|
|
||||||
// initializeApp(firebaseAdminConfig);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export const adminAuth = getAuth();
|
|
||||||
@@ -76,7 +76,7 @@ const routes: RouteData[] = [
|
|||||||
{
|
{
|
||||||
path: "upload",
|
path: "upload",
|
||||||
name: "upload",
|
name: "upload",
|
||||||
component: () => import("./add/Add.vue"),
|
component: () => import("./upload/Upload.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
head: {
|
head: {
|
||||||
title: 'Upload - Holistream',
|
title: 'Upload - Holistream',
|
||||||
@@ -86,7 +86,7 @@ const routes: RouteData[] = [
|
|||||||
{
|
{
|
||||||
path: "video",
|
path: "video",
|
||||||
name: "video",
|
name: "video",
|
||||||
component: () => import("./add/Add.vue"),
|
component: () => import("./video/Videos.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
head: {
|
head: {
|
||||||
title: 'Videos - Holistream',
|
title: 'Videos - Holistream',
|
||||||
@@ -99,7 +99,7 @@ const routes: RouteData[] = [
|
|||||||
{
|
{
|
||||||
path: "plans",
|
path: "plans",
|
||||||
name: "plans",
|
name: "plans",
|
||||||
component: () => import("./add/Add.vue"),
|
component: () => import("./plans/Plans.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
head: {
|
head: {
|
||||||
title: 'Plans & Billing',
|
title: 'Plans & Billing',
|
||||||
|
|||||||
117
src/routes/plans/Plans.vue
Normal file
117
src/routes/plans/Plans.vue
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-3xl font-bold mb-6">Choose Your Plan</h1>
|
||||||
|
<div v-if="loading" class="flex justify-center">
|
||||||
|
<div class="i-svg-spinners-180-ring-with-bg text-4xl"></div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="error" class="text-red-500">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
<div v-for="plan in plans" :key="plan.id" class="border rounded-lg p-6 shadow-md bg-white dark:bg-gray-800 flex flex-col">
|
||||||
|
<h2 class="text-xl font-semibold mb-2">{{ plan.name }}</h2>
|
||||||
|
<div class="text-3xl font-bold mb-4">${{ plan.price }}<span class="text-sm font-normal text-gray-500">/{{ plan.cycle }}</span></div>
|
||||||
|
<p class="text-gray-600 dark:text-gray-300 mb-6 flex-grow">{{ plan.description }}</p>
|
||||||
|
|
||||||
|
<ul class="mb-6 space-y-2">
|
||||||
|
<li class="flex items-center">
|
||||||
|
<span class="i-heroicons-check-circle text-green-500 mr-2"></span>
|
||||||
|
<span>Storage: {{ formatBytes(plan.storage_limit || 0) }}</span>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center">
|
||||||
|
<span class="i-heroicons-check-circle text-green-500 mr-2"></span>
|
||||||
|
<span>Max Duration: {{ formatDuration(plan.duration_limit || 0) }}</span>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center">
|
||||||
|
<span class="i-heroicons-check-circle text-green-500 mr-2"></span>
|
||||||
|
<span>Uploads: {{ plan.upload_limit }} / day</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="subscribe(plan)"
|
||||||
|
class="w-full py-2 px-4 bg-primary-600 hover:bg-primary-700 text-white rounded transition-colors disabled:opacity-50"
|
||||||
|
:disabled="subscribing === plan.id"
|
||||||
|
>
|
||||||
|
<span v-if="subscribing === plan.id" class="i-svg-spinners-180-ring-with-bg mr-2"></span>
|
||||||
|
{{ subscribing === plan.id ? 'Processing...' : 'Subscribe' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { client, type ModelPlan } from '@/api/client';
|
||||||
|
|
||||||
|
const plans = ref<ModelPlan[]>([]);
|
||||||
|
const loading = ref(true);
|
||||||
|
const error = ref<string | null>(null);
|
||||||
|
const subscribing = ref<string | null>(null);
|
||||||
|
|
||||||
|
const fetchPlans = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
try {
|
||||||
|
const response = await client.plans.plansList();
|
||||||
|
if (response.data && Array.isArray(response.data)) {
|
||||||
|
plans.value = response.data;
|
||||||
|
} else {
|
||||||
|
// Fallback or handle unexpected structure?
|
||||||
|
// Based on client.ts it returns response.data as ModelPlan[] directly in the custom wrapper?
|
||||||
|
// Wait, client.ts says: r.data = data. So if the response matches schema, it's inside data.
|
||||||
|
// Let's re-read client.ts.
|
||||||
|
// plansList defined as request<ResponseResponse & { data?: ModelPlan[] }>
|
||||||
|
// So yes, response.data which is the body, and inside that, there is a data property.
|
||||||
|
// wait, let's check client.ts generated code again.
|
||||||
|
|
||||||
|
// plansList returns Promise<HttpResponse<...>>
|
||||||
|
// HttpResponse has .data property which IS the body.
|
||||||
|
// The body type is ResponseResponse & { data?: ModelPlan[] }
|
||||||
|
// So we access response.data.data
|
||||||
|
plans.value = response.data.data || [];
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(err);
|
||||||
|
error.value = err.message || 'Failed to load plans';
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscribe = async (plan: ModelPlan) => {
|
||||||
|
if (!plan.id) return;
|
||||||
|
subscribing.value = plan.id;
|
||||||
|
try {
|
||||||
|
// Mock payment for now as per plan, or call API if ready
|
||||||
|
// client.payments.paymentsCreate({ amount: plan.price || 0, plan_id: plan.id });
|
||||||
|
await client.payments.paymentsCreate({
|
||||||
|
amount: plan.price || 0,
|
||||||
|
plan_id: plan.id
|
||||||
|
});
|
||||||
|
alert(`Successfully subscribed to ${plan.name}`);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(err);
|
||||||
|
alert('Failed to subscribe: ' + (err.message || 'Unknown error'));
|
||||||
|
} finally {
|
||||||
|
subscribing.value = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatBytes = (bytes: number) => {
|
||||||
|
if (bytes === 0) return '0 B';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDuration = (seconds: number) => {
|
||||||
|
return `${Math.floor(seconds / 60)} mins`;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchPlans();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
181
src/routes/upload/Upload.vue
Normal file
181
src/routes/upload/Upload.vue
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
<template>
|
||||||
|
<div class="p-6 max-w-2xl mx-auto">
|
||||||
|
<h1 class="text-3xl font-bold mb-6">Upload Video</h1>
|
||||||
|
|
||||||
|
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md">
|
||||||
|
<div v-if="!file" class="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-10 text-center hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer" @drop.prevent="handleDrop" @dragover.prevent>
|
||||||
|
<input type="file" id="fileInput" class="hidden" accept="video/*" @change="handleFileSelect">
|
||||||
|
<label for="fileInput" class="cursor-pointer">
|
||||||
|
<span class="i-heroicons-cloud-arrow-up text-6xl text-gray-400 mb-4 inline-block"></span>
|
||||||
|
<p class="text-xl font-medium text-gray-700 dark:text-gray-200">Drag & drop your video here</p>
|
||||||
|
<p class="text-sm text-gray-500 mt-2">or click to browse</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="space-y-6">
|
||||||
|
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded">
|
||||||
|
<div class="flex items-center space-x-3 overflow-hidden">
|
||||||
|
<span class="i-heroicons-video-camera text-2xl text-primary-500 flex-shrink-0"></span>
|
||||||
|
<span class="truncate font-medium">{{ file.name }}</span>
|
||||||
|
<span class="text-xs text-gray-500 flex-shrink-0">({{ formatBytes(file.size) }})</span>
|
||||||
|
</div>
|
||||||
|
<button @click="resetFile" class="text-red-500 hover:text-red-700 flex-shrink-0" :disabled="uploading">
|
||||||
|
<span class="i-heroicons-x-mark text-xl"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">Title</label>
|
||||||
|
<input v-model="form.title" type="text" class="w-full rounded border-gray-300 dark:border-gray-600 dark:bg-gray-700 p-2" placeholder="My Awesome Video">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">Description</label>
|
||||||
|
<textarea v-model="form.description" class="w-full rounded border-gray-300 dark:border-gray-600 dark:bg-gray-700 p-2" rows="3" placeholder="Describe your video..."></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="uploadError" class="text-red-500 text-sm">
|
||||||
|
{{ uploadError }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="uploading" class="space-y-2">
|
||||||
|
<div class="flex justify-between text-sm">
|
||||||
|
<span>Uploading...</span>
|
||||||
|
<span>{{ uploadProgress }}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="h-2 bg-gray-200 rounded-full overflow-hidden">
|
||||||
|
<div class="h-full bg-primary-600 transition-all duration-300" :style="{ width: `${uploadProgress}%` }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="startUpload"
|
||||||
|
class="w-full py-3 bg-primary-600 hover:bg-primary-700 text-white rounded font-medium disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
:disabled="uploading || !form.title"
|
||||||
|
>
|
||||||
|
{{ uploading ? 'Uploading...' : 'Upload Video' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { client } from '@/api/client';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const file = ref<File | null>(null);
|
||||||
|
const uploading = ref(false);
|
||||||
|
const uploadProgress = ref(0);
|
||||||
|
const uploadError = ref<string | null>(null);
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
title: '',
|
||||||
|
description: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleFileSelect = (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement;
|
||||||
|
if (target.files && target.files.length > 0) {
|
||||||
|
file.value = target.files[0];
|
||||||
|
form.title = file.value.name.replace(/\.[^/.]+$/, ""); // Default title from filename
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrop = (event: DragEvent) => {
|
||||||
|
if (event.dataTransfer?.files.length) {
|
||||||
|
file.value = event.dataTransfer.files[0];
|
||||||
|
form.title = file.value.name.replace(/\.[^/.]+$/, "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetFile = () => {
|
||||||
|
file.value = null;
|
||||||
|
form.title = '';
|
||||||
|
form.description = '';
|
||||||
|
uploadError.value = null;
|
||||||
|
uploadProgress.value = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const startUpload = async () => {
|
||||||
|
if (!file.value || !form.title) return;
|
||||||
|
|
||||||
|
uploading.value = true;
|
||||||
|
uploadError.value = null;
|
||||||
|
uploadProgress.value = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Get Presigned URL
|
||||||
|
const presignedResponse = await client.videos.uploadUrlCreate({
|
||||||
|
filename: file.value.name,
|
||||||
|
content_type: file.value.type,
|
||||||
|
size: file.value.size
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for data in response
|
||||||
|
const data = (presignedResponse.data as any).data || presignedResponse.data;
|
||||||
|
if (!data || !data.url) {
|
||||||
|
throw new Error('Failed to get upload URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadUrl = data.url;
|
||||||
|
const finalVideoUrl = data.key || uploadUrl.split('?')[0]; // Assuming key is returned or can be derived
|
||||||
|
|
||||||
|
// 2. Upload file to S3 (using XMLHttpRequest for progress)
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('PUT', uploadUrl, true);
|
||||||
|
xhr.setRequestHeader('Content-Type', file.value!.type);
|
||||||
|
|
||||||
|
xhr.upload.onprogress = (e) => {
|
||||||
|
if (e.lengthComputable) {
|
||||||
|
uploadProgress.value = Math.round((e.loaded / e.total) * 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onload = () => {
|
||||||
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Upload failed with status ${xhr.status}`));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = () => reject(new Error('Network error during upload'));
|
||||||
|
xhr.send(file.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Create Video Record
|
||||||
|
await client.videos.videosCreate({
|
||||||
|
title: form.title,
|
||||||
|
description: form.description,
|
||||||
|
size: file.value.size,
|
||||||
|
format: file.value.type,
|
||||||
|
url: finalVideoUrl,
|
||||||
|
// duration is optional, server might process it
|
||||||
|
});
|
||||||
|
|
||||||
|
// Redirect to videos page
|
||||||
|
router.push({ name: 'video' });
|
||||||
|
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(err);
|
||||||
|
uploadError.value = err.message || 'Upload failed';
|
||||||
|
} finally {
|
||||||
|
uploading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatBytes = (bytes: number) => {
|
||||||
|
if (bytes === 0) return '0 B';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
};
|
||||||
|
</script>
|
||||||
103
src/routes/video/Videos.vue
Normal file
103
src/routes/video/Videos.vue
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h1 class="text-3xl font-bold">My Videos</h1>
|
||||||
|
<router-link to="/upload" class="bg-primary-600 hover:bg-primary-700 text-white px-4 py-2 rounded transition-colors flex items-center">
|
||||||
|
<span class="i-heroicons-cloud-arrow-up mr-2"></span>
|
||||||
|
Upload Video
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="loading" class="flex justify-center">
|
||||||
|
<div class="i-svg-spinners-180-ring-with-bg text-4xl"></div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="error" class="text-red-500">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
<div v-else-if="videos.length === 0" class="text-center text-gray-500 py-10">
|
||||||
|
<p>No videos found. Upload your first video!</p>
|
||||||
|
</div>
|
||||||
|
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||||
|
<div v-for="video in videos" :key="video.id" class="border rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow bg-white dark:bg-gray-800">
|
||||||
|
<div class="aspect-video bg-gray-200 dark:bg-gray-700 relative group">
|
||||||
|
<img v-if="video.thumbnail" :src="video.thumbnail" :alt="video.title" class="w-full h-full object-cover">
|
||||||
|
<div v-else class="w-full h-full flex items-center justify-center text-gray-400">
|
||||||
|
<span class="i-heroicons-video-camera text-4xl"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
|
||||||
|
<button class="text-white bg-primary-600 p-2 rounded-full hover:bg-primary-700" title="Play">
|
||||||
|
<span class="i-heroicons-play-20-solid text-xl"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span class="absolute bottom-2 right-2 bg-black/70 text-white text-xs px-1 rounded">{{ formatDuration(video.duration || 0) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-4">
|
||||||
|
<h3 class="font-semibold text-lg mb-1 truncate" :title="video.title">{{ video.title }}</h3>
|
||||||
|
<p class="text-sm text-gray-500 mb-2 truncate">{{ video.description || 'No description' }}</p>
|
||||||
|
<div class="flex justify-between items-center text-xs text-gray-400">
|
||||||
|
<span>{{ formatDate(video.created_at) }}</span>
|
||||||
|
<span :class="getStatusClass(video.status)">{{ video.status }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { client, type ModelVideo } from '@/api/client';
|
||||||
|
|
||||||
|
const videos = ref<ModelVideo[]>([]);
|
||||||
|
const loading = ref(true);
|
||||||
|
const error = ref<string | null>(null);
|
||||||
|
|
||||||
|
const fetchVideos = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
try {
|
||||||
|
const response = await client.videos.videosList({ page: 1, limit: 50 });
|
||||||
|
// Based on docs.json, schema might be incomplete, assuming standard response structure
|
||||||
|
// response.data is the body. The body should have 'data' containing the list?
|
||||||
|
const body = response.data as any; // Cast because generated type didn't have data field
|
||||||
|
if (body.data && Array.isArray(body.data)) {
|
||||||
|
videos.value = body.data;
|
||||||
|
} else if (Array.isArray(body)) {
|
||||||
|
videos.value = body;
|
||||||
|
} else {
|
||||||
|
console.warn('Unexpected video list format:', body);
|
||||||
|
videos.value = [];
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(err);
|
||||||
|
error.value = err.message || 'Failed to load videos';
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDuration = (seconds: number) => {
|
||||||
|
const m = Math.floor(seconds / 60);
|
||||||
|
const s = Math.floor(seconds % 60);
|
||||||
|
return `${m}:${s.toString().padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (dateString?: string) => {
|
||||||
|
if (!dateString) return '';
|
||||||
|
return new Date(dateString).toLocaleDateString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusClass = (status?: string) => {
|
||||||
|
switch(status?.toLowerCase()) {
|
||||||
|
case 'ready': return 'text-green-500';
|
||||||
|
case 'processing': return 'text-yellow-500';
|
||||||
|
case 'failed': return 'text-red-500';
|
||||||
|
default: return 'text-gray-500';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchVideos();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,115 +1,128 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
// import { client } from '@/api/rpcclient'; // client no longer used for auth actions
|
import { ref } from 'vue';
|
||||||
import { ref, onMounted } from 'vue';
|
import { client, ResponseResponse, type ModelUser } from '@/api/client';
|
||||||
import { emailAuth, signUp, auth, googleAuth } from '@/lib/firebase';
|
|
||||||
import { onAuthStateChanged, signOut, User as FirebaseUser } from 'firebase/auth';
|
|
||||||
|
|
||||||
interface User {
|
|
||||||
id: string;
|
|
||||||
username: string;
|
|
||||||
email: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
const user = ref<User | null>(null);
|
const user = ref<ModelUser | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const error = ref<string | null>(null);
|
const error = ref<string | null>(null);
|
||||||
const initialized = ref(false);
|
const initialized = ref(false);
|
||||||
|
|
||||||
// Check auth status on init using Firebase observer
|
// Initial check for session could go here if there was a /me endpoint or token check
|
||||||
async function init() {
|
async function init() {
|
||||||
if (initialized.value) return;
|
// if (initialized.value) return;
|
||||||
|
const response = await client.request<
|
||||||
return new Promise<void>((resolve) => {
|
ResponseResponse & {
|
||||||
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
|
data?: ModelUser;
|
||||||
if (currentUser) {
|
},
|
||||||
user.value = mapFirebaseUser(currentUser);
|
ResponseResponse
|
||||||
} else {
|
>({
|
||||||
user.value = null;
|
path: '/me',
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
// user.value = response.data?.data;
|
||||||
|
if (response.data?.data) {
|
||||||
|
user.value = response.data.data;
|
||||||
}
|
}
|
||||||
initialized.value = true;
|
initialized.value = true;
|
||||||
resolve();
|
|
||||||
// We could unsubscribe here if we only want initial load,
|
|
||||||
// but keeping it listens for changes (token refresh etc)
|
|
||||||
// However, 'init' usually implies just ONCE waiter.
|
|
||||||
// For reactivity, user.value is updated.
|
|
||||||
});
|
|
||||||
// Note: onAuthStateChanged returns an unsubscribe function.
|
|
||||||
// If we want to keep listening, we shouldn't unsubscribe immediately,
|
|
||||||
// but for 'await auth.init()' we just want to wait for the first known state.
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapFirebaseUser(fwUser: FirebaseUser): User {
|
|
||||||
return {
|
|
||||||
id: fwUser.uid,
|
|
||||||
username: fwUser.email?.split('@')[0] || 'user', // fallback
|
|
||||||
email: fwUser.email || '',
|
|
||||||
name: fwUser.displayName || fwUser.email?.split('@')[0] || 'User'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function login(username: string, password: string) {
|
async function login(username: string, password: string) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
error.value = null;
|
error.value = null;
|
||||||
// Assuming username is email for Firebase, or we need to look it up?
|
try {
|
||||||
// Firebase works with Email. If input is username, this might fail.
|
const response = await client.auth.loginCreate({
|
||||||
// For now assume email.
|
email: username,
|
||||||
return emailAuth(username, password).then((userCredential) => {
|
password: password
|
||||||
user.value = mapFirebaseUser(userCredential.user);
|
});
|
||||||
|
|
||||||
|
// Expected response structure: { data: { code: 200, data: User, message: "..." } } based on typical wrapper + schema
|
||||||
|
// BUT client.ts generated code typically returns the body directly in .data property of HttpResponse
|
||||||
|
// And schema says ResponseResponse has 'data': {}
|
||||||
|
// So: response.data (HttpResponse body) -> .data (ResponseResponse payload)
|
||||||
|
|
||||||
|
const body = response.data as any; // Cast to access potential 'data' property if types are loose
|
||||||
|
if (body && body.data) {
|
||||||
|
user.value = body.data;
|
||||||
router.push('/');
|
router.push('/');
|
||||||
}).catch((e: any) => {
|
} else {
|
||||||
|
throw new Error('Login failed: No user data received');
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
error.value = 'Login failed: ' + (e.message || 'Unknown error');
|
error.value = 'Login failed: ' + (e.message || 'Unknown error');
|
||||||
throw e;
|
throw e;
|
||||||
}).finally(() => {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loginWithGoogle() {
|
async function loginWithGoogle() {
|
||||||
loading.value = true;
|
// usually this initiates a redirect loop.
|
||||||
error.value = null;
|
// Doing it via client.request might follow redirect or return html.
|
||||||
return googleAuth().then((result) => {
|
// Best to just redirect the window.
|
||||||
user.value = mapFirebaseUser(result.user);
|
window.location.href = `${client.baseUrl}/auth/google/login`;
|
||||||
router.push('/');
|
|
||||||
}).catch((e: any) => {
|
|
||||||
console.error(e);
|
|
||||||
error.value = 'Google Login failed';
|
|
||||||
throw e;
|
|
||||||
}).finally(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function register(username: string, email: string, password: string) {
|
async function register(username: string, email: string, password: string) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
error.value = null;
|
error.value = null;
|
||||||
return signUp(email, password).then((fwUser) => {
|
try {
|
||||||
// update profile with username?
|
const response = await client.auth.registerCreate({
|
||||||
// updateProfile(fwUser, { displayName: username });
|
username,
|
||||||
user.value = mapFirebaseUser(fwUser);
|
email,
|
||||||
router.push('/');
|
password
|
||||||
}).catch((e: any) => {
|
});
|
||||||
|
|
||||||
|
// Check success
|
||||||
|
const body = response.data as any;
|
||||||
|
if (response.ok) {
|
||||||
|
// Auto login or redirect to login?
|
||||||
|
// Usually register returns success, user must login.
|
||||||
|
router.push('/login');
|
||||||
|
} else {
|
||||||
|
throw new Error(body.message || 'Registration failed');
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
error.value = 'Registration failed: ' + (e.message || 'Unknown error');
|
error.value = 'Registration failed: ' + (e.message || 'Unknown error');
|
||||||
throw e;
|
throw e;
|
||||||
}).finally(() => {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logout() {
|
async function logout() {
|
||||||
return signOut(auth).then(() => {
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
await client.auth.logoutCreate();
|
||||||
user.value = null;
|
user.value = null;
|
||||||
router.push('/');
|
router.push('/login');
|
||||||
})
|
} catch (e: any) {
|
||||||
|
console.error('Logout error', e);
|
||||||
|
// Force local logout anyway
|
||||||
|
user.value = null;
|
||||||
|
router.push('/login');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user, loading, error, initialized, init, login, loginWithGoogle, register, logout, $reset: () => {
|
user,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
initialized,
|
||||||
|
init,
|
||||||
|
login,
|
||||||
|
loginWithGoogle,
|
||||||
|
register,
|
||||||
|
logout,
|
||||||
|
$reset: () => {
|
||||||
user.value = null;
|
user.value = null;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
error.value = null;
|
error.value = null;
|
||||||
|
|||||||
7
src/type.d.ts
vendored
7
src/type.d.ts
vendored
@@ -2,10 +2,5 @@
|
|||||||
/// <reference types="unplugin-vue-components/types/vue" />
|
/// <reference types="unplugin-vue-components/types/vue" />
|
||||||
|
|
||||||
declare module "@httpClientAdapter" {
|
declare module "@httpClientAdapter" {
|
||||||
import { TinyRpcClientAdapter } from "@hiogawa/tiny-rpc";
|
export const customFetch: (url: string, options: RequestInit) => Promise<Response>;
|
||||||
export function httpClientAdapter(opts: {
|
|
||||||
url: string;
|
|
||||||
pathsForGET?: string[];
|
|
||||||
headers?: () => Promise<{ Authorization?: undefined; } | { Authorization: string; }>
|
|
||||||
}): TinyRpcClientAdapter;
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user