diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 2616476..973aeab 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -5,7 +5,8 @@ "mcp__ide__getDiagnostics", "Bash(bun install:*)", "Bash(bun preview:*)", - "Bash(curl:*)" + "Bash(curl:*)", + "Bash(python -:*)" ] } } diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..2ae2aaa --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,19 @@ +version: v2 +plugins: + # - remote: buf.build/protocolbuffers/go + # out: internal/gen/proto + # opt: + # - paths=source_relative + # - remote: buf.build/grpc/go + # out: internal/gen/proto + # opt: + # - paths=source_relative + - remote: buf.build/community/stephenh-ts-proto + out: ../stream-ui/src/server/utils/proto + opt: + - env=node + - esModuleInterop=true + - outputServices=grpc-js + - useOptionals=all + - forceLong=number + - useDate=string diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..c7e30e3 --- /dev/null +++ b/buf.yaml @@ -0,0 +1,9 @@ +version: v2 +modules: + - path: proto +lint: + use: + - STANDARD +breaking: + use: + - FILE diff --git a/bun.lock b/bun.lock index beba169..88a7457 100644 --- a/bun.lock +++ b/bun.lock @@ -5,8 +5,11 @@ "": { "name": "holistream", "dependencies": { + "@bufbuild/protobuf": "^2.11.0", + "@grpc/grpc-js": "^1.14.3", "@hattip/adapter-node": "^0.0.49", "@hono/node-server": "^1.19.11", + "@hono/zod-validator": "^0.7.6", "@pinia/colada": "^0.21.7", "@unhead/vue": "^2.1.10", "@vueuse/core": "^14.2.1", @@ -25,6 +28,7 @@ }, "devDependencies": { "@cloudflare/vite-plugin": "^1.26.0", + "@types/bun": "^1.3.10", "@types/node": "^25.3.3", "@vitejs/plugin-vue": "^6.0.4", "@vitejs/plugin-vue-jsx": "^5.1.4", @@ -94,6 +98,8 @@ "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + "@bufbuild/protobuf": ["@bufbuild/protobuf@2.11.0", "", {}, "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ=="], + "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="], "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.14.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "^1.20260218.0" }, "optionalPeers": ["workerd"] }, "sha512-XKAkWhi1nBdNsSEoNG9nkcbyvfUrSjSf+VYVPfOto3gLTZVc3F4g6RASCMh6IixBKCG2yDgZKQIHGKtjcnLnKg=="], @@ -170,6 +176,10 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], + "@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.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=="], + "@hattip/adapter-node": ["@hattip/adapter-node@0.0.49", "", { "dependencies": { "@hattip/core": "0.0.49", "@hattip/polyfills": "0.0.49", "@hattip/walk": "0.0.49" } }, "sha512-BE+Y8Q4U0YcH34FZUYU4DssGKOaZLbNL0zK57Z41UZp0m9kS79ZIolBmjjpPhTVpIlRY3Rs+uhXbVXKk7mUcJA=="], "@hattip/core": ["@hattip/core@0.0.49", "", {}, "sha512-3/ZJtC17cv8m6Sph8+nw4exUp9yhEf2Shi7HK6AHSUSBtaaQXZ9rJBVxTfZj3PGNOR/P49UBXOym/52WYKFTJQ=="], @@ -182,6 +192,8 @@ "@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="], + "@hono/zod-validator": ["@hono/zod-validator@0.7.6", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw=="], + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], "@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="], @@ -246,6 +258,8 @@ "@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=="], + "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], + "@kamilkisiela/fast-url-parser": ["@kamilkisiela/fast-url-parser@1.1.4", "", {}, "sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew=="], "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], @@ -304,6 +318,26 @@ "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="], + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], + "@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="], "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.6", "", { "os": "android", "cpu": "arm64" }, "sha512-kvjTSWGcrv+BaR2vge57rsKiYdVR8V8CoS0vgKrc570qRBfty4bT+1X0z3j2TaVV+kAYzA0PjeB9+mdZyqUZlg=="], @@ -340,6 +374,8 @@ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/node": ["@types/node@25.3.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ=="], @@ -438,6 +474,10 @@ "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "ast-kit": ["ast-kit@2.2.0", "", { "dependencies": { "@babel/parser": "^7.28.5", "pathe": "^2.0.3" } }, "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw=="], "ast-walker-scope": ["ast-walker-scope@0.8.3", "", { "dependencies": { "@babel/parser": "^7.28.4", "ast-kit": "^2.1.3" } }, "sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg=="], @@ -452,6 +492,8 @@ "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + "bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="], + "busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="], "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], @@ -460,8 +502,14 @@ "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], @@ -492,6 +540,8 @@ "electron-to-chromium": ["electron-to-chromium@1.5.302", "", {}, "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg=="], + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], @@ -516,6 +566,8 @@ "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + "gzip-size": ["gzip-size@6.0.0", "", { "dependencies": { "duplexer": "^0.1.2" } }, "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q=="], "hono": ["hono@4.12.5", "", {}, "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg=="], @@ -528,6 +580,8 @@ "i18next-vue": ["i18next-vue@5.4.0", "", { "peerDependencies": { "i18next": ">=23", "vue": "^3.4.38" } }, "sha512-GDj0Xvmis5Xgcvo9gMBJMgJCtewYMLZP6gAEPDDGCMjA+QeB4uS4qUf1MK79mkz/FukhaJdC+nlj0y1qk6NO2Q=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-mobile": ["is-mobile@5.0.0", "", {}, "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ=="], "is-what": ["is-what@5.5.0", "", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="], @@ -568,6 +622,10 @@ "local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="], + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "magic-regexp": ["magic-regexp@0.10.0", "", { "dependencies": { "estree-walker": "^3.0.3", "magic-string": "^0.30.12", "mlly": "^1.7.2", "regexp-tree": "^0.1.27", "type-level-regexp": "~0.1.17", "ufo": "^1.5.4", "unplugin": "^2.0.0" } }, "sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg=="], @@ -628,12 +686,16 @@ "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], "regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], "rolldown": ["rolldown@1.0.0-rc.6", "", { "dependencies": { "@oxc-project/types": "=0.115.0", "@rolldown/pluginutils": "1.0.0-rc.6" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.6", "@rolldown/binding-darwin-arm64": "1.0.0-rc.6", "@rolldown/binding-darwin-x64": "1.0.0-rc.6", "@rolldown/binding-freebsd-x64": "1.0.0-rc.6", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.6", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.6", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.6", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.6", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.6", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.6", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.6", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.6", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.6" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-B8vFPV1ADyegoYfhg+E7RAucYKv0xdVlwYYsIJgfPNeiSxZGWNxts9RqhyGzC11ULK/VaeXyKezGCwpMiH8Ktw=="], @@ -652,6 +714,10 @@ "streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="], + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], "superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="], @@ -720,12 +786,20 @@ "wrangler": ["wrangler@4.70.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.14.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260301.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260301.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260226.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-PNDZ9o4e+B5x+1bUbz62Hmwz6G9lw+I9pnYe/AguLddJFjfIyt2cmFOUOb3eOZSoXsrhcEPUg2YidYIbVwUkfw=="], + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="], "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="], diff --git a/golang.tar.gz b/golang.tar.gz deleted file mode 100644 index 1738f41..0000000 Binary files a/golang.tar.gz and /dev/null differ diff --git a/package.json b/package.json index 360251a..7db5c9f 100644 --- a/package.json +++ b/package.json @@ -2,16 +2,19 @@ "name": "holistream", "type": "module", "scripts": { - "dev": "bun vite", - "build": "bun vite build", - "preview": "bun vite preview", + "dev": "bunx --bun vite", + "build": "bunx --bun vite build", + "preview": "bunx --bun vite preview", "deploy": "wrangler deploy", "cf-typegen": "wrangler types --env-interface CloudflareBindings", "tail": "wrangler tail" }, "dependencies": { + "@bufbuild/protobuf": "^2.11.0", + "@grpc/grpc-js": "^1.14.3", "@hattip/adapter-node": "^0.0.49", "@hono/node-server": "^1.19.11", + "@hono/zod-validator": "^0.7.6", "@pinia/colada": "^0.21.7", "@unhead/vue": "^2.1.10", "@vueuse/core": "^14.2.1", @@ -30,6 +33,7 @@ }, "devDependencies": { "@cloudflare/vite-plugin": "^1.26.0", + "@types/bun": "^1.3.10", "@types/node": "^25.3.3", "@vitejs/plugin-vue": "^6.0.4", "@vitejs/plugin-vue-jsx": "^5.1.4", diff --git a/proto/v1/common.proto b/proto/v1/common.proto new file mode 100644 index 0000000..3e3eda4 --- /dev/null +++ b/proto/v1/common.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +package stream.common.v1; + +option go_package = "stream/proto/gen/go/common/v1;commonv1"; + +import "google/protobuf/timestamp.proto"; + +message RequestContext { + string user_id = 1; + string email = 2; + string role = 3; + string request_id = 4; + string source = 5; +} + +message PaginationRequest { + int32 page = 1; + int32 page_size = 2; +} + +message PaginationResponse { + int32 page = 1; + int32 page_size = 2; + int64 total = 3; +} + +message Money { + double amount = 1; + string currency = 2; +} + +message Empty {} + +message IdRequest { + string id = 1; +} + +message DeleteResponse { + string message = 1; +} + +message TimestampRange { + google.protobuf.Timestamp from = 1; + google.protobuf.Timestamp to = 2; +} diff --git a/proto/v1/user.proto b/proto/v1/user.proto new file mode 100644 index 0000000..2d07c41 --- /dev/null +++ b/proto/v1/user.proto @@ -0,0 +1,130 @@ +syntax = "proto3"; + +package stream.User.v1; + +option go_package = "stream/proto/gen/go/User/v1;Userv1"; + +import "google/protobuf/timestamp.proto"; + +service UserService { + // User CRUD + rpc GetUser(GetUserRequest) returns (GetUserResponse); + rpc GetUserByEmail(GetUserByEmailRequest) returns (GetUserResponse); + rpc ListUsers(ListUsersRequest) returns (ListUsersResponse); + rpc CreateUser(CreateUserRequest) returns (CreateUserResponse); + rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse); + rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse); + + // Preferences + rpc GetPreferences(GetPreferencesRequest) returns (GetPreferencesResponse); + rpc UpsertPreferences(UpsertPreferencesRequest) returns (UpsertPreferencesResponse); +} + +// ─── User Messages ─────────────────────────────────────────────────────────── + +message GetUserRequest { + string id = 1; +} + +message GetUserByEmailRequest { + string email = 1; +} + +message GetUserResponse { + User user = 1; +} + +message ListUsersRequest { + int32 page = 1; + int32 page_size = 2; + string role = 3; // optional filter +} + +message ListUsersResponse { + repeated User users = 1; + int32 total = 2; + int32 page = 3; + int32 page_size = 4; +} + +message CreateUserRequest { + string email = 1; + optional string username = 2; + optional string password = 3; +} + +message CreateUserResponse { + User user = 1; +} + +message UpdateUserRequest { + string id = 1; + optional string username = 2; + optional string avatar = 3; + optional string role = 4; + optional string plan_id = 5; +} + +message UpdateUserResponse { + User user = 1; +} + +message DeleteUserRequest { + string id = 1; +} + +message DeleteUserResponse { + bool success = 1; +} + +// ─── Preferences Messages ──────────────────────────────────────────────────── + +message GetPreferencesRequest { + string user_id = 1; +} + +message GetPreferencesResponse { + Preferences preferences = 1; +} + +message UpsertPreferencesRequest { + Preferences preferences = 1; +} + +message UpsertPreferencesResponse { + Preferences preferences = 1; +} + +// ─── Core Models ───────────────────────────────────────────────────────────── + +message User { + string id = 1; + string email = 2; + string password = 3; + optional string username = 4; + optional string avatar = 5; + optional string role = 6; + optional string google_id = 7; + int64 storage_used = 8; + optional string plan_id = 9; + optional google.protobuf.Timestamp created_at = 10; + google.protobuf.Timestamp updated_at = 11; +} + +message Preferences { + string user_id = 1; + optional string language = 2; + optional string locale = 3; + optional bool email_notifications = 4; + optional bool push_notifications = 5; + optional bool marketing_notifications = 6; + optional bool telegram_notifications = 7; + optional bool autoplay = 8; + optional bool loop = 9; + optional bool muted = 10; + optional bool show_controls = 11; + optional bool pip = 12; + optional bool airplay = 13; + optional bool chromecast = 14; + optional bool encrytion_m3u8 = 15; +} diff --git a/src/index.tsx b/src/index.tsx index 130bfad..7c6fe95 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,12 +7,12 @@ import { registerManifestRoutes } from './server/routes/manifest'; import { registerMergeRoutes } from './server/routes/merge'; import { registerSSRRoutes } from './server/routes/ssr'; import { registerWellKnownRoutes } from './server/routes/wellKnown'; - +import { setupServices } from './server/services/grpcClient'; const app = new Hono(); // Global middlewares setupMiddlewares(app); - +setupServices(app); // API proxy middleware (handles /r/*) app.use(apiProxyMiddleware); // Routes diff --git a/src/server/middlewares/setup.ts b/src/server/middlewares/setup.ts index 49eda93..a8bf372 100644 --- a/src/server/middlewares/setup.ts +++ b/src/server/middlewares/setup.ts @@ -1,17 +1,36 @@ -import { contextStorage } from 'hono/context-storage'; -import { cors } from 'hono/cors'; -import isMobile from 'is-mobile'; -import type { Hono } from 'hono'; -import { languageDetector } from 'hono/language'; +import { RedisClient } from "bun"; +import type { Hono } from "hono"; +import { contextStorage } from "hono/context-storage"; +import { cors } from "hono/cors"; +import { languageDetector } from "hono/language"; +import isMobile from "is-mobile"; +type AppFetch = ( + input: string | Request | URL, + requestInit?: RequestInit +) => Response | Promise; + +declare module "hono" { + interface ContextVariableMap { + fetch: AppFetch; + isMobile: boolean; + redis: RedisClient; + } +} + +const client = new RedisClient("redis://:pass123@47.84.62.226:6379/3"); export function setupMiddlewares(app: Hono) { - app.use('*', languageDetector({ - supportedLanguages: ['vi', 'en'], - fallbackLanguage: 'en', - lookupCookie: 'i18next', - lookupFromHeaderKey: 'accept-language', - order: ['cookie', 'header'], - }) ,contextStorage()); + app.use( + "*", + languageDetector({ + supportedLanguages: ["vi", "en"], + fallbackLanguage: "en", + lookupCookie: "i18next", + lookupFromHeaderKey: "accept-language", + order: ["cookie", "header"], + }), + contextStorage() + ); app.use(cors(), async (c, next) => { c.set("fetch", app.request.bind(app)); @@ -24,4 +43,15 @@ export function setupMiddlewares(app: Hono) { c.set("isMobile", isMobile({ ua })); await next(); }); + app.use(async (c, next) => { + client + .connect() + .then(() => { + c.set("redis", client); + return next(); + }) + .catch((e) => { + console.error("Failed to connect to Redis", e); + }); + }); } diff --git a/src/server/routes/auth.ts b/src/server/routes/auth.ts new file mode 100644 index 0000000..f0d9cd9 --- /dev/null +++ b/src/server/routes/auth.ts @@ -0,0 +1,79 @@ +import { zValidator } from '@hono/zod-validator'; +import { Hono } from 'hono'; +import z from 'zod'; +import { getUserServiceClient } from '../services/grpcClient'; +// authGroup := r.Group("/auth") +// { +// authGroup.POST("/login", authHandler.Login) +// authGroup.POST("/register", authHandler.Register) +// authGroup.POST("/forgot-password", authHandler.ForgotPassword) +// authGroup.POST("/reset-password", authHandler.ResetPassword) +// authGroup.GET("/google/login", authHandler.LoginGoogle) +// authGroup.GET("/google/callback", authHandler.GoogleCallback) +// } +const authRoute = new Hono(); +authRoute.post('/login', zValidator('json', z.object({ email: z.email(), password: z.string().min(6) })), async (c) => { + const data = c.req.valid("json") + const user = await getUserServiceClient().getUserByEmail(data); + if (!user) { + return c.json({ error: 'Invalid email or password' }, 401); + } + if (user.password !== data.password) { + return c.json({ error: 'Invalid email or password' }, 401); + } + // const user = await getUserServiceClient().getUserByEmail({ email }, (err, response) => { + // if (err) { + // console.error("Error fetching user by email", err); + // return null; + // } + // return response; + // }); + // return c.json({ message: 'Login endpoint' }); +}); +authRoute.post('/register', zValidator('json', z.object({ email: z.email(), password: z.string().min(6) })), async (c) => { + return c.json({ message: 'Register endpoint' }); +}); +authRoute.post('/forgot-password', zValidator('json', z.object({ email: z.email() })), async (c) => { + return c.json({ message: 'Forgot Password endpoint' }); +}); +authRoute.post('/reset-password', zValidator('json', z.object({ token: z.string(), password: z.string().min(6) })), async (c) => { + return c.json({ message: 'Reset Password endpoint' }); +}); +authRoute.get('/google/login', zValidator('query', z.object({ redirect_uri: z.string().url() })), async (c) => { + return c.json({ message: 'Google Login endpoint' }); +}); +authRoute.get('/google/callback', zValidator('query', z.object({ code: z.string(), state: z.string() })), async (c) => { + return c.json({ message: 'Google Callback endpoint' }); +}); +export function registerAuthRoutes(app: Hono) { + + // app.post('/merge', async (c) => { + // try { + // const body = await c.req.json(); + // const { filename, chunks, size } = body; + + // if (!filename || !Array.isArray(chunks) || chunks.length === 0) { + // return c.json({ error: 'invalid payload' }, 400); + // } + + // const hostError = validateChunkUrls(chunks); + // if (hostError) return c.json({ error: hostError }, 400); + + // const manifest = createManifest(filename, chunks, size); + // await saveManifest(manifest); + + // return c.json({ + // status: 'ok', + // id: manifest.id, + // filename: manifest.filename, + // total_parts: manifest.total_parts, + // size: manifest.size, + // playback_url: `/display/${manifest.id}`, + // play_url: `/play/index/${manifest.id}`, + // manifest_url: `/manifest/${manifest.id}`, + // }); + // } catch (e: any) { + // return c.json({ error: e?.message ?? String(e) }, 500); + // } + // }); +} diff --git a/src/server/services/grpcClient.ts b/src/server/services/grpcClient.ts new file mode 100644 index 0000000..1a498a3 --- /dev/null +++ b/src/server/services/grpcClient.ts @@ -0,0 +1,60 @@ +import { ChannelCredentials, credentials } from "@grpc/grpc-js"; +import { tryGetContext } from "hono/context-storage"; +import { Hono } from "node_modules/hono/dist/types/hono"; +import { PromisifiedClient, promisifyClient } from "../utils/grpcHelper"; +import { UserServiceClient } from "../utils/proto/v1/user"; +declare module "hono" { + interface ContextVariableMap { + userServiceClient: PromisifiedClient; + } +} +const DEFAULT_GRPC_ADDRESS = '127.0.0.1:9000'; + +const grpcAddress = () => process.env.STREAM_API_GRPC_ADDR || DEFAULT_GRPC_ADDRESS; +let sharedCredentials: ChannelCredentials | undefined; +const getCredentials = () => { + if (!sharedCredentials) { + sharedCredentials = credentials.createInsecure(); + } + return sharedCredentials; +}; +export const getUserServiceClient = () => { + const context = tryGetContext(); + if (context) { + return context.get("userServiceClient"); + } + throw new Error("No context available to get UserServiceClient"); +}; +// (method) UserServiceClient.getUserByEmail(request: GetUserByEmailRequest, callback: (error: ServiceError | null, response: GetUserResponse) => void): ClientUnaryCall (+2 overloads) + +// const unaryCall = ( +// executor: ( +// metadata: Metadata, +// options: Partial, +// callback: (error: ServiceError | null, response: TResponse) => void, +// ) => { metadata?: Metadata; trailer?: Metadata }, +// ): Promise => { +// // const { metadata } = createMetadataFromContext(); + +// return new Promise((resolve, reject) => { +// executor({ +// deadline: Date.now() + 10_000, +// }, (error, response) => { +// if (error) { +// reject(normalizeGrpcError(error)); +// return; +// } + +// // appendSetCookiesToResponse(call.metadata?.get('set-cookie') ?? []); +// resolve(response); +// }); +// }); +// }; + + +export const setupServices = (app: Hono) => { + app.use("*", async (c, next) => { + c.set("userServiceClient", promisifyClient(new UserServiceClient(grpcAddress(), getCredentials()))); + await next(); + }); +} \ No newline at end of file diff --git a/src/server/utils/grpcHelper.ts b/src/server/utils/grpcHelper.ts new file mode 100644 index 0000000..c255d4c --- /dev/null +++ b/src/server/utils/grpcHelper.ts @@ -0,0 +1,109 @@ +import { ClientUnaryCall, ServiceError, status } from "@grpc/grpc-js"; +type UnaryCallback = ( + error: ServiceError | null, + response: TRes +) => void; + +type UnaryLike = ( + req: TReq, + callback: UnaryCallback +) => ClientUnaryCall; + +type RequestOf = T extends ( + req: infer TReq, + callback: UnaryCallback +) => ClientUnaryCall + ? TReq + : never; + +type ResponseOf = T extends ( + req: any, + callback: UnaryCallback +) => ClientUnaryCall + ? TRes + : never; + +/** + * Lấy ra overload đúng dạng (req, callback) => ClientUnaryCall + */ +type ExtractUnaryOverload = Extract>; + +export type PromisifiedClient = { + [K in keyof TClient as ExtractUnaryOverload extends never + ? never + : K]: ( + req: RequestOf> + ) => Promise>>; +}; + + +const grpcCodeToHttpStatus = (code?: number) => { + switch (code) { + case status.INVALID_ARGUMENT: + return 400; + case status.UNAUTHENTICATED: + return 401; + case status.PERMISSION_DENIED: + return 403; + case status.NOT_FOUND: + return 404; + default: + return 500; + } +}; +const normalizeGrpcError = (error: ServiceError) => { + const normalized = new Error(error.details || error.message) as Error & { + status?: number; + code?: number; + body?: { code?: number; message?: string; data?: unknown }; + }; + normalized.code = error.code; + normalized.status = grpcCodeToHttpStatus(error.code); + + const trailerBody = error.metadata?.get('x-error-body')?.[0]; + if (typeof trailerBody === 'string' && trailerBody) { + try { + normalized.body = JSON.parse(trailerBody) as { code?: number; message?: string; data?: unknown }; + if (normalized.body?.message) { + normalized.message = normalized.body.message; + } + if (typeof normalized.body?.code === 'number') { + normalized.status = normalized.body.code; + } + } catch { + // ignore malformed structured error payloads + } + } + + return normalized; +}; +export function promisifyClient( + client: TClient +): PromisifiedClient { + const proto = Object.getPrototypeOf(client); + const result: Record = {}; + + for (const key of Object.getOwnPropertyNames(proto)) { + if (key === "constructor") continue; + + const value = (client as Record)[key]; + if (typeof value !== "function") continue; + + result[key] = (req: unknown) => + new Promise((resolve, reject) => { + (value as Function).call( + client, + req, + (error: ServiceError | null, response: unknown) => { + if (error) { + reject(normalizeGrpcError(error)); + return; + } + resolve(response); + } + ); + }); + } + + return result as PromisifiedClient; +} \ No newline at end of file diff --git a/src/server/utils/index.ts b/src/server/utils/index.ts new file mode 100644 index 0000000..218c419 --- /dev/null +++ b/src/server/utils/index.ts @@ -0,0 +1,38 @@ +import { RedisClient } from "bun"; +import { tryGetContext } from "hono/context-storage"; +import { + setCookie +} from 'hono/cookie'; +import { JWTProvider } from "./token"; +export const redisClient = (): RedisClient => { + const context = tryGetContext(); + const redis = context?.get("redis") as RedisClient | undefined; + if (!redis) { + throw new Error("Redis client not found in context"); + } + return redis; +}; + +export async function generateAndSetTokens(userID: string, email: string, role: string) { + const redis = redisClient(); + const context = tryGetContext(); + await JWTProvider("your-secret-key").generateTokenPair(userID, email, role).then((td) => { + redis.set("refresh_uuid:" + td.refreshUUID, userID, "EX", td.rtExpires - Math.floor(Date.now() / 1000)); + if (context) { + setCookie(context, "access_token", td.accessToken, { + expires: new Date(td.atExpires * 1000), + httpOnly: true, + secure: false, + path: "/", + }); + setCookie(context, "refresh_token", td.refreshToken, { + expires: new Date(td.rtExpires * 1000), + httpOnly: true, + secure: false, + path: "/", + }); + } + }).catch((e) => { + console.error("Error generating tokens", e); + }); +} \ No newline at end of file diff --git a/src/server/utils/proto/google/protobuf/timestamp.ts b/src/server/utils/proto/google/protobuf/timestamp.ts new file mode 100644 index 0000000..00808e3 --- /dev/null +++ b/src/server/utils/proto/google/protobuf/timestamp.ts @@ -0,0 +1,231 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v2.11.4 +// protoc unknown +// source: google/protobuf/timestamp.proto + +/* eslint-disable */ +import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; + +export const protobufPackage = "google.protobuf"; + +/** + * A Timestamp represents a point in time independent of any time zone or local + * calendar, encoded as a count of seconds and fractions of seconds at + * nanosecond resolution. The count is relative to an epoch at UTC midnight on + * January 1, 1970, in the proleptic Gregorian calendar which extends the + * Gregorian calendar backwards to year one. + * + * All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap + * second table is needed for interpretation, using a [24-hour linear + * smear](https://developers.google.com/time/smear). + * + * The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By + * restricting to that range, we ensure that we can convert to and from [RFC + * 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. + * + * # Examples + * + * Example 1: Compute Timestamp from POSIX `time()`. + * + * Timestamp timestamp; + * timestamp.set_seconds(time(NULL)); + * timestamp.set_nanos(0); + * + * Example 2: Compute Timestamp from POSIX `gettimeofday()`. + * + * struct timeval tv; + * gettimeofday(&tv, NULL); + * + * Timestamp timestamp; + * timestamp.set_seconds(tv.tv_sec); + * timestamp.set_nanos(tv.tv_usec * 1000); + * + * Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. + * + * FILETIME ft; + * GetSystemTimeAsFileTime(&ft); + * UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + * + * // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z + * // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. + * Timestamp timestamp; + * timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); + * timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); + * + * Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. + * + * long millis = System.currentTimeMillis(); + * + * Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) + * .setNanos((int) ((millis % 1000) * 1000000)).build(); + * + * Example 5: Compute Timestamp from Java `Instant.now()`. + * + * Instant now = Instant.now(); + * + * Timestamp timestamp = + * Timestamp.newBuilder().setSeconds(now.getEpochSecond()) + * .setNanos(now.getNano()).build(); + * + * Example 6: Compute Timestamp from current time in Python. + * + * timestamp = Timestamp() + * timestamp.GetCurrentTime() + * + * # JSON Mapping + * + * In JSON format, the Timestamp type is encoded as a string in the + * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the + * format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" + * where {year} is always expressed using four digits while {month}, {day}, + * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional + * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), + * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone + * is required. A proto3 JSON serializer should always use UTC (as indicated by + * "Z") when printing the Timestamp type and a proto3 JSON parser should be + * able to accept both UTC and other timezones (as indicated by an offset). + * + * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past + * 01:30 UTC on January 15, 2017. + * + * In JavaScript, one can convert a Date object to this format using the + * standard + * [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) + * method. In Python, a standard `datetime.datetime` object can be converted + * to this format using + * [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with + * the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use + * the Joda Time's [`ISODateTimeFormat.dateTime()`]( + * http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() + * ) to obtain a formatter capable of generating timestamps in this format. + */ +export interface Timestamp { + /** + * Represents seconds of UTC time since Unix epoch + * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + * 9999-12-31T23:59:59Z inclusive. + */ + seconds?: + | number + | undefined; + /** + * Non-negative fractions of a second at nanosecond resolution. Negative + * second values with fractions must still have non-negative nanos values + * that count forward in time. Must be from 0 to 999,999,999 + * inclusive. + */ + nanos?: number | undefined; +} + +function createBaseTimestamp(): Timestamp { + return { seconds: 0, nanos: 0 }; +} + +export const Timestamp: MessageFns = { + encode(message: Timestamp, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.seconds !== undefined && message.seconds !== 0) { + writer.uint32(8).int64(message.seconds); + } + if (message.nanos !== undefined && message.nanos !== 0) { + writer.uint32(16).int32(message.nanos); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Timestamp { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseTimestamp(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.seconds = longToNumber(reader.int64()); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.nanos = reader.int32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Timestamp { + return { + seconds: isSet(object.seconds) ? globalThis.Number(object.seconds) : 0, + nanos: isSet(object.nanos) ? globalThis.Number(object.nanos) : 0, + }; + }, + + toJSON(message: Timestamp): unknown { + const obj: any = {}; + if (message.seconds !== undefined && message.seconds !== 0) { + obj.seconds = Math.round(message.seconds); + } + if (message.nanos !== undefined && message.nanos !== 0) { + obj.nanos = Math.round(message.nanos); + } + return obj; + }, + + create, I>>(base?: I): Timestamp { + return Timestamp.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Timestamp { + const message = createBaseTimestamp(); + message.seconds = object.seconds ?? 0; + message.nanos = object.nanos ?? 0; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.Array> + : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function longToNumber(int64: { toString(): string }): number { + const num = globalThis.Number(int64.toString()); + if (num > globalThis.Number.MAX_SAFE_INTEGER) { + throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER"); + } + if (num < globalThis.Number.MIN_SAFE_INTEGER) { + throw new globalThis.Error("Value is smaller than Number.MIN_SAFE_INTEGER"); + } + return num; +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} + +export interface MessageFns { + encode(message: T, writer?: BinaryWriter): BinaryWriter; + decode(input: BinaryReader | Uint8Array, length?: number): T; + fromJSON(object: any): T; + toJSON(message: T): unknown; + create, I>>(base?: I): T; + fromPartial, I>>(object: I): T; +} diff --git a/src/server/utils/proto/v1/common.ts b/src/server/utils/proto/v1/common.ts new file mode 100644 index 0000000..8c4b3a6 --- /dev/null +++ b/src/server/utils/proto/v1/common.ts @@ -0,0 +1,719 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v2.11.4 +// protoc unknown +// source: v1/common.proto + +/* eslint-disable */ +import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; +import { Timestamp } from "../google/protobuf/timestamp"; + +export const protobufPackage = "stream.common.v1"; + +export interface RequestContext { + userId?: string | undefined; + email?: string | undefined; + role?: string | undefined; + requestId?: string | undefined; + source?: string | undefined; +} + +export interface PaginationRequest { + page?: number | undefined; + pageSize?: number | undefined; +} + +export interface PaginationResponse { + page?: number | undefined; + pageSize?: number | undefined; + total?: number | undefined; +} + +export interface Money { + amount?: number | undefined; + currency?: string | undefined; +} + +export interface Empty { +} + +export interface IdRequest { + id?: string | undefined; +} + +export interface DeleteResponse { + message?: string | undefined; +} + +export interface TimestampRange { + from?: string | undefined; + to?: string | undefined; +} + +function createBaseRequestContext(): RequestContext { + return { userId: "", email: "", role: "", requestId: "", source: "" }; +} + +export const RequestContext: MessageFns = { + encode(message: RequestContext, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.userId !== undefined && message.userId !== "") { + writer.uint32(10).string(message.userId); + } + if (message.email !== undefined && message.email !== "") { + writer.uint32(18).string(message.email); + } + if (message.role !== undefined && message.role !== "") { + writer.uint32(26).string(message.role); + } + if (message.requestId !== undefined && message.requestId !== "") { + writer.uint32(34).string(message.requestId); + } + if (message.source !== undefined && message.source !== "") { + writer.uint32(42).string(message.source); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): RequestContext { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseRequestContext(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.userId = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.email = reader.string(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.role = reader.string(); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.requestId = reader.string(); + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.source = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): RequestContext { + return { + userId: isSet(object.userId) + ? globalThis.String(object.userId) + : isSet(object.user_id) + ? globalThis.String(object.user_id) + : "", + email: isSet(object.email) ? globalThis.String(object.email) : "", + role: isSet(object.role) ? globalThis.String(object.role) : "", + requestId: isSet(object.requestId) + ? globalThis.String(object.requestId) + : isSet(object.request_id) + ? globalThis.String(object.request_id) + : "", + source: isSet(object.source) ? globalThis.String(object.source) : "", + }; + }, + + toJSON(message: RequestContext): unknown { + const obj: any = {}; + if (message.userId !== undefined && message.userId !== "") { + obj.userId = message.userId; + } + if (message.email !== undefined && message.email !== "") { + obj.email = message.email; + } + if (message.role !== undefined && message.role !== "") { + obj.role = message.role; + } + if (message.requestId !== undefined && message.requestId !== "") { + obj.requestId = message.requestId; + } + if (message.source !== undefined && message.source !== "") { + obj.source = message.source; + } + return obj; + }, + + create, I>>(base?: I): RequestContext { + return RequestContext.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): RequestContext { + const message = createBaseRequestContext(); + message.userId = object.userId ?? ""; + message.email = object.email ?? ""; + message.role = object.role ?? ""; + message.requestId = object.requestId ?? ""; + message.source = object.source ?? ""; + return message; + }, +}; + +function createBasePaginationRequest(): PaginationRequest { + return { page: 0, pageSize: 0 }; +} + +export const PaginationRequest: MessageFns = { + encode(message: PaginationRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.page !== undefined && message.page !== 0) { + writer.uint32(8).int32(message.page); + } + if (message.pageSize !== undefined && message.pageSize !== 0) { + writer.uint32(16).int32(message.pageSize); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): PaginationRequest { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBasePaginationRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.page = reader.int32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.pageSize = reader.int32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): PaginationRequest { + return { + page: isSet(object.page) ? globalThis.Number(object.page) : 0, + pageSize: isSet(object.pageSize) + ? globalThis.Number(object.pageSize) + : isSet(object.page_size) + ? globalThis.Number(object.page_size) + : 0, + }; + }, + + toJSON(message: PaginationRequest): unknown { + const obj: any = {}; + if (message.page !== undefined && message.page !== 0) { + obj.page = Math.round(message.page); + } + if (message.pageSize !== undefined && message.pageSize !== 0) { + obj.pageSize = Math.round(message.pageSize); + } + return obj; + }, + + create, I>>(base?: I): PaginationRequest { + return PaginationRequest.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): PaginationRequest { + const message = createBasePaginationRequest(); + message.page = object.page ?? 0; + message.pageSize = object.pageSize ?? 0; + return message; + }, +}; + +function createBasePaginationResponse(): PaginationResponse { + return { page: 0, pageSize: 0, total: 0 }; +} + +export const PaginationResponse: MessageFns = { + encode(message: PaginationResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.page !== undefined && message.page !== 0) { + writer.uint32(8).int32(message.page); + } + if (message.pageSize !== undefined && message.pageSize !== 0) { + writer.uint32(16).int32(message.pageSize); + } + if (message.total !== undefined && message.total !== 0) { + writer.uint32(24).int64(message.total); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): PaginationResponse { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBasePaginationResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.page = reader.int32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.pageSize = reader.int32(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.total = longToNumber(reader.int64()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): PaginationResponse { + return { + page: isSet(object.page) ? globalThis.Number(object.page) : 0, + pageSize: isSet(object.pageSize) + ? globalThis.Number(object.pageSize) + : isSet(object.page_size) + ? globalThis.Number(object.page_size) + : 0, + total: isSet(object.total) ? globalThis.Number(object.total) : 0, + }; + }, + + toJSON(message: PaginationResponse): unknown { + const obj: any = {}; + if (message.page !== undefined && message.page !== 0) { + obj.page = Math.round(message.page); + } + if (message.pageSize !== undefined && message.pageSize !== 0) { + obj.pageSize = Math.round(message.pageSize); + } + if (message.total !== undefined && message.total !== 0) { + obj.total = Math.round(message.total); + } + return obj; + }, + + create, I>>(base?: I): PaginationResponse { + return PaginationResponse.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): PaginationResponse { + const message = createBasePaginationResponse(); + message.page = object.page ?? 0; + message.pageSize = object.pageSize ?? 0; + message.total = object.total ?? 0; + return message; + }, +}; + +function createBaseMoney(): Money { + return { amount: 0, currency: "" }; +} + +export const Money: MessageFns = { + encode(message: Money, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.amount !== undefined && message.amount !== 0) { + writer.uint32(9).double(message.amount); + } + if (message.currency !== undefined && message.currency !== "") { + writer.uint32(18).string(message.currency); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Money { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMoney(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 9) { + break; + } + + message.amount = reader.double(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.currency = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Money { + return { + amount: isSet(object.amount) ? globalThis.Number(object.amount) : 0, + currency: isSet(object.currency) ? globalThis.String(object.currency) : "", + }; + }, + + toJSON(message: Money): unknown { + const obj: any = {}; + if (message.amount !== undefined && message.amount !== 0) { + obj.amount = message.amount; + } + if (message.currency !== undefined && message.currency !== "") { + obj.currency = message.currency; + } + return obj; + }, + + create, I>>(base?: I): Money { + return Money.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Money { + const message = createBaseMoney(); + message.amount = object.amount ?? 0; + message.currency = object.currency ?? ""; + return message; + }, +}; + +function createBaseEmpty(): Empty { + return {}; +} + +export const Empty: MessageFns = { + encode(_: Empty, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Empty { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseEmpty(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(_: any): Empty { + return {}; + }, + + toJSON(_: Empty): unknown { + const obj: any = {}; + return obj; + }, + + create, I>>(base?: I): Empty { + return Empty.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(_: I): Empty { + const message = createBaseEmpty(); + return message; + }, +}; + +function createBaseIdRequest(): IdRequest { + return { id: "" }; +} + +export const IdRequest: MessageFns = { + encode(message: IdRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.id !== undefined && message.id !== "") { + writer.uint32(10).string(message.id); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): IdRequest { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseIdRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.id = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): IdRequest { + return { id: isSet(object.id) ? globalThis.String(object.id) : "" }; + }, + + toJSON(message: IdRequest): unknown { + const obj: any = {}; + if (message.id !== undefined && message.id !== "") { + obj.id = message.id; + } + return obj; + }, + + create, I>>(base?: I): IdRequest { + return IdRequest.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): IdRequest { + const message = createBaseIdRequest(); + message.id = object.id ?? ""; + return message; + }, +}; + +function createBaseDeleteResponse(): DeleteResponse { + return { message: "" }; +} + +export const DeleteResponse: MessageFns = { + encode(message: DeleteResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.message !== undefined && message.message !== "") { + writer.uint32(10).string(message.message); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): DeleteResponse { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseDeleteResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.message = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): DeleteResponse { + return { message: isSet(object.message) ? globalThis.String(object.message) : "" }; + }, + + toJSON(message: DeleteResponse): unknown { + const obj: any = {}; + if (message.message !== undefined && message.message !== "") { + obj.message = message.message; + } + return obj; + }, + + create, I>>(base?: I): DeleteResponse { + return DeleteResponse.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): DeleteResponse { + const message = createBaseDeleteResponse(); + message.message = object.message ?? ""; + return message; + }, +}; + +function createBaseTimestampRange(): TimestampRange { + return { from: undefined, to: undefined }; +} + +export const TimestampRange: MessageFns = { + encode(message: TimestampRange, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.from !== undefined) { + Timestamp.encode(toTimestamp(message.from), writer.uint32(10).fork()).join(); + } + if (message.to !== undefined) { + Timestamp.encode(toTimestamp(message.to), writer.uint32(18).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): TimestampRange { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseTimestampRange(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.from = fromTimestamp(Timestamp.decode(reader, reader.uint32())); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.to = fromTimestamp(Timestamp.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): TimestampRange { + return { + from: isSet(object.from) ? globalThis.String(object.from) : undefined, + to: isSet(object.to) ? globalThis.String(object.to) : undefined, + }; + }, + + toJSON(message: TimestampRange): unknown { + const obj: any = {}; + if (message.from !== undefined) { + obj.from = message.from; + } + if (message.to !== undefined) { + obj.to = message.to; + } + return obj; + }, + + create, I>>(base?: I): TimestampRange { + return TimestampRange.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): TimestampRange { + const message = createBaseTimestampRange(); + message.from = object.from ?? undefined; + message.to = object.to ?? undefined; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.Array> + : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function toTimestamp(dateStr: string): Timestamp { + const date = new globalThis.Date(dateStr); + const seconds = Math.trunc(date.getTime() / 1_000); + const nanos = (date.getTime() % 1_000) * 1_000_000; + return { seconds, nanos }; +} + +function fromTimestamp(t: Timestamp): string { + let millis = (t.seconds || 0) * 1_000; + millis += (t.nanos || 0) / 1_000_000; + return new globalThis.Date(millis).toISOString(); +} + +function longToNumber(int64: { toString(): string }): number { + const num = globalThis.Number(int64.toString()); + if (num > globalThis.Number.MAX_SAFE_INTEGER) { + throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER"); + } + if (num < globalThis.Number.MIN_SAFE_INTEGER) { + throw new globalThis.Error("Value is smaller than Number.MIN_SAFE_INTEGER"); + } + return num; +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} + +export interface MessageFns { + encode(message: T, writer?: BinaryWriter): BinaryWriter; + decode(input: BinaryReader | Uint8Array, length?: number): T; + fromJSON(object: any): T; + toJSON(message: T): unknown; + create, I>>(base?: I): T; + fromPartial, I>>(object: I): T; +} diff --git a/src/server/utils/proto/v1/user.ts b/src/server/utils/proto/v1/user.ts new file mode 100644 index 0000000..4314af7 --- /dev/null +++ b/src/server/utils/proto/v1/user.ts @@ -0,0 +1,2071 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v2.11.4 +// protoc unknown +// source: v1/user.proto + +/* eslint-disable */ +import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; +import { + type CallOptions, + type ChannelCredentials, + Client, + type ClientOptions, + type ClientUnaryCall, + type handleUnaryCall, + makeGenericClientConstructor, + type Metadata, + type ServiceError, + type UntypedServiceImplementation, +} from "@grpc/grpc-js"; +import { Timestamp } from "../google/protobuf/timestamp"; + +export const protobufPackage = "stream.User.v1"; + +export interface GetUserRequest { + id?: string | undefined; +} + +export interface GetUserByEmailRequest { + email?: string | undefined; +} + +export interface GetUserResponse { + user?: User | undefined; +} + +export interface ListUsersRequest { + page?: number | undefined; + pageSize?: + | number + | undefined; + /** optional filter */ + role?: string | undefined; +} + +export interface ListUsersResponse { + users?: User[] | undefined; + total?: number | undefined; + page?: number | undefined; + pageSize?: number | undefined; +} + +export interface CreateUserRequest { + email?: string | undefined; + username?: string | undefined; + password?: string | undefined; +} + +export interface CreateUserResponse { + user?: User | undefined; +} + +export interface UpdateUserRequest { + id?: string | undefined; + username?: string | undefined; + avatar?: string | undefined; + role?: string | undefined; + planId?: string | undefined; +} + +export interface UpdateUserResponse { + user?: User | undefined; +} + +export interface DeleteUserRequest { + id?: string | undefined; +} + +export interface DeleteUserResponse { + success?: boolean | undefined; +} + +export interface GetPreferencesRequest { + userId?: string | undefined; +} + +export interface GetPreferencesResponse { + preferences?: Preferences | undefined; +} + +export interface UpsertPreferencesRequest { + preferences?: Preferences | undefined; +} + +export interface UpsertPreferencesResponse { + preferences?: Preferences | undefined; +} + +export interface User { + id?: string | undefined; + email?: string | undefined; + password?: string | undefined; + username?: string | undefined; + avatar?: string | undefined; + role?: string | undefined; + googleId?: string | undefined; + storageUsed?: number | undefined; + planId?: string | undefined; + createdAt?: string | undefined; + updatedAt?: string | undefined; +} + +export interface Preferences { + userId?: string | undefined; + language?: string | undefined; + locale?: string | undefined; + emailNotifications?: boolean | undefined; + pushNotifications?: boolean | undefined; + marketingNotifications?: boolean | undefined; + telegramNotifications?: boolean | undefined; + autoplay?: boolean | undefined; + loop?: boolean | undefined; + muted?: boolean | undefined; + showControls?: boolean | undefined; + pip?: boolean | undefined; + airplay?: boolean | undefined; + chromecast?: boolean | undefined; + encrytionM3u8?: boolean | undefined; +} + +function createBaseGetUserRequest(): GetUserRequest { + return { id: "" }; +} + +export const GetUserRequest: MessageFns = { + encode(message: GetUserRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.id !== undefined && message.id !== "") { + writer.uint32(10).string(message.id); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): GetUserRequest { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGetUserRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.id = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): GetUserRequest { + return { id: isSet(object.id) ? globalThis.String(object.id) : "" }; + }, + + toJSON(message: GetUserRequest): unknown { + const obj: any = {}; + if (message.id !== undefined && message.id !== "") { + obj.id = message.id; + } + return obj; + }, + + create, I>>(base?: I): GetUserRequest { + return GetUserRequest.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GetUserRequest { + const message = createBaseGetUserRequest(); + message.id = object.id ?? ""; + return message; + }, +}; + +function createBaseGetUserByEmailRequest(): GetUserByEmailRequest { + return { email: "" }; +} + +export const GetUserByEmailRequest: MessageFns = { + encode(message: GetUserByEmailRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.email !== undefined && message.email !== "") { + writer.uint32(10).string(message.email); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): GetUserByEmailRequest { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGetUserByEmailRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.email = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): GetUserByEmailRequest { + return { email: isSet(object.email) ? globalThis.String(object.email) : "" }; + }, + + toJSON(message: GetUserByEmailRequest): unknown { + const obj: any = {}; + if (message.email !== undefined && message.email !== "") { + obj.email = message.email; + } + return obj; + }, + + create, I>>(base?: I): GetUserByEmailRequest { + return GetUserByEmailRequest.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GetUserByEmailRequest { + const message = createBaseGetUserByEmailRequest(); + message.email = object.email ?? ""; + return message; + }, +}; + +function createBaseGetUserResponse(): GetUserResponse { + return { user: undefined }; +} + +export const GetUserResponse: MessageFns = { + encode(message: GetUserResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.user !== undefined) { + User.encode(message.user, writer.uint32(10).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): GetUserResponse { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGetUserResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.user = User.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): GetUserResponse { + return { user: isSet(object.user) ? User.fromJSON(object.user) : undefined }; + }, + + toJSON(message: GetUserResponse): unknown { + const obj: any = {}; + if (message.user !== undefined) { + obj.user = User.toJSON(message.user); + } + return obj; + }, + + create, I>>(base?: I): GetUserResponse { + return GetUserResponse.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GetUserResponse { + const message = createBaseGetUserResponse(); + message.user = (object.user !== undefined && object.user !== null) ? User.fromPartial(object.user) : undefined; + return message; + }, +}; + +function createBaseListUsersRequest(): ListUsersRequest { + return { page: 0, pageSize: 0, role: "" }; +} + +export const ListUsersRequest: MessageFns = { + encode(message: ListUsersRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.page !== undefined && message.page !== 0) { + writer.uint32(8).int32(message.page); + } + if (message.pageSize !== undefined && message.pageSize !== 0) { + writer.uint32(16).int32(message.pageSize); + } + if (message.role !== undefined && message.role !== "") { + writer.uint32(26).string(message.role); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): ListUsersRequest { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseListUsersRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.page = reader.int32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.pageSize = reader.int32(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.role = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): ListUsersRequest { + return { + page: isSet(object.page) ? globalThis.Number(object.page) : 0, + pageSize: isSet(object.pageSize) + ? globalThis.Number(object.pageSize) + : isSet(object.page_size) + ? globalThis.Number(object.page_size) + : 0, + role: isSet(object.role) ? globalThis.String(object.role) : "", + }; + }, + + toJSON(message: ListUsersRequest): unknown { + const obj: any = {}; + if (message.page !== undefined && message.page !== 0) { + obj.page = Math.round(message.page); + } + if (message.pageSize !== undefined && message.pageSize !== 0) { + obj.pageSize = Math.round(message.pageSize); + } + if (message.role !== undefined && message.role !== "") { + obj.role = message.role; + } + return obj; + }, + + create, I>>(base?: I): ListUsersRequest { + return ListUsersRequest.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): ListUsersRequest { + const message = createBaseListUsersRequest(); + message.page = object.page ?? 0; + message.pageSize = object.pageSize ?? 0; + message.role = object.role ?? ""; + return message; + }, +}; + +function createBaseListUsersResponse(): ListUsersResponse { + return { users: [], total: 0, page: 0, pageSize: 0 }; +} + +export const ListUsersResponse: MessageFns = { + encode(message: ListUsersResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.users !== undefined && message.users.length !== 0) { + for (const v of message.users) { + User.encode(v!, writer.uint32(10).fork()).join(); + } + } + if (message.total !== undefined && message.total !== 0) { + writer.uint32(16).int32(message.total); + } + if (message.page !== undefined && message.page !== 0) { + writer.uint32(24).int32(message.page); + } + if (message.pageSize !== undefined && message.pageSize !== 0) { + writer.uint32(32).int32(message.pageSize); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): ListUsersResponse { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseListUsersResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + const el = User.decode(reader, reader.uint32()); + if (el !== undefined) { + message.users!.push(el); + } + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.total = reader.int32(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.page = reader.int32(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.pageSize = reader.int32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): ListUsersResponse { + return { + users: globalThis.Array.isArray(object?.users) ? object.users.map((e: any) => User.fromJSON(e)) : [], + total: isSet(object.total) ? globalThis.Number(object.total) : 0, + page: isSet(object.page) ? globalThis.Number(object.page) : 0, + pageSize: isSet(object.pageSize) + ? globalThis.Number(object.pageSize) + : isSet(object.page_size) + ? globalThis.Number(object.page_size) + : 0, + }; + }, + + toJSON(message: ListUsersResponse): unknown { + const obj: any = {}; + if (message.users?.length) { + obj.users = message.users.map((e) => User.toJSON(e)); + } + if (message.total !== undefined && message.total !== 0) { + obj.total = Math.round(message.total); + } + if (message.page !== undefined && message.page !== 0) { + obj.page = Math.round(message.page); + } + if (message.pageSize !== undefined && message.pageSize !== 0) { + obj.pageSize = Math.round(message.pageSize); + } + return obj; + }, + + create, I>>(base?: I): ListUsersResponse { + return ListUsersResponse.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): ListUsersResponse { + const message = createBaseListUsersResponse(); + message.users = object.users?.map((e) => User.fromPartial(e)) || []; + message.total = object.total ?? 0; + message.page = object.page ?? 0; + message.pageSize = object.pageSize ?? 0; + return message; + }, +}; + +function createBaseCreateUserRequest(): CreateUserRequest { + return { email: "", username: undefined, password: undefined }; +} + +export const CreateUserRequest: MessageFns = { + encode(message: CreateUserRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.email !== undefined && message.email !== "") { + writer.uint32(10).string(message.email); + } + if (message.username !== undefined) { + writer.uint32(18).string(message.username); + } + if (message.password !== undefined) { + writer.uint32(26).string(message.password); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): CreateUserRequest { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseCreateUserRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.email = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.username = reader.string(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.password = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): CreateUserRequest { + return { + email: isSet(object.email) ? globalThis.String(object.email) : "", + username: isSet(object.username) ? globalThis.String(object.username) : undefined, + password: isSet(object.password) ? globalThis.String(object.password) : undefined, + }; + }, + + toJSON(message: CreateUserRequest): unknown { + const obj: any = {}; + if (message.email !== undefined && message.email !== "") { + obj.email = message.email; + } + if (message.username !== undefined) { + obj.username = message.username; + } + if (message.password !== undefined) { + obj.password = message.password; + } + return obj; + }, + + create, I>>(base?: I): CreateUserRequest { + return CreateUserRequest.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): CreateUserRequest { + const message = createBaseCreateUserRequest(); + message.email = object.email ?? ""; + message.username = object.username ?? undefined; + message.password = object.password ?? undefined; + return message; + }, +}; + +function createBaseCreateUserResponse(): CreateUserResponse { + return { user: undefined }; +} + +export const CreateUserResponse: MessageFns = { + encode(message: CreateUserResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.user !== undefined) { + User.encode(message.user, writer.uint32(10).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): CreateUserResponse { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseCreateUserResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.user = User.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): CreateUserResponse { + return { user: isSet(object.user) ? User.fromJSON(object.user) : undefined }; + }, + + toJSON(message: CreateUserResponse): unknown { + const obj: any = {}; + if (message.user !== undefined) { + obj.user = User.toJSON(message.user); + } + return obj; + }, + + create, I>>(base?: I): CreateUserResponse { + return CreateUserResponse.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): CreateUserResponse { + const message = createBaseCreateUserResponse(); + message.user = (object.user !== undefined && object.user !== null) ? User.fromPartial(object.user) : undefined; + return message; + }, +}; + +function createBaseUpdateUserRequest(): UpdateUserRequest { + return { id: "", username: undefined, avatar: undefined, role: undefined, planId: undefined }; +} + +export const UpdateUserRequest: MessageFns = { + encode(message: UpdateUserRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.id !== undefined && message.id !== "") { + writer.uint32(10).string(message.id); + } + if (message.username !== undefined) { + writer.uint32(18).string(message.username); + } + if (message.avatar !== undefined) { + writer.uint32(26).string(message.avatar); + } + if (message.role !== undefined) { + writer.uint32(34).string(message.role); + } + if (message.planId !== undefined) { + writer.uint32(42).string(message.planId); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): UpdateUserRequest { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseUpdateUserRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.id = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.username = reader.string(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.avatar = reader.string(); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.role = reader.string(); + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.planId = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): UpdateUserRequest { + return { + id: isSet(object.id) ? globalThis.String(object.id) : "", + username: isSet(object.username) ? globalThis.String(object.username) : undefined, + avatar: isSet(object.avatar) ? globalThis.String(object.avatar) : undefined, + role: isSet(object.role) ? globalThis.String(object.role) : undefined, + planId: isSet(object.planId) + ? globalThis.String(object.planId) + : isSet(object.plan_id) + ? globalThis.String(object.plan_id) + : undefined, + }; + }, + + toJSON(message: UpdateUserRequest): unknown { + const obj: any = {}; + if (message.id !== undefined && message.id !== "") { + obj.id = message.id; + } + if (message.username !== undefined) { + obj.username = message.username; + } + if (message.avatar !== undefined) { + obj.avatar = message.avatar; + } + if (message.role !== undefined) { + obj.role = message.role; + } + if (message.planId !== undefined) { + obj.planId = message.planId; + } + return obj; + }, + + create, I>>(base?: I): UpdateUserRequest { + return UpdateUserRequest.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): UpdateUserRequest { + const message = createBaseUpdateUserRequest(); + message.id = object.id ?? ""; + message.username = object.username ?? undefined; + message.avatar = object.avatar ?? undefined; + message.role = object.role ?? undefined; + message.planId = object.planId ?? undefined; + return message; + }, +}; + +function createBaseUpdateUserResponse(): UpdateUserResponse { + return { user: undefined }; +} + +export const UpdateUserResponse: MessageFns = { + encode(message: UpdateUserResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.user !== undefined) { + User.encode(message.user, writer.uint32(10).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): UpdateUserResponse { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseUpdateUserResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.user = User.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): UpdateUserResponse { + return { user: isSet(object.user) ? User.fromJSON(object.user) : undefined }; + }, + + toJSON(message: UpdateUserResponse): unknown { + const obj: any = {}; + if (message.user !== undefined) { + obj.user = User.toJSON(message.user); + } + return obj; + }, + + create, I>>(base?: I): UpdateUserResponse { + return UpdateUserResponse.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): UpdateUserResponse { + const message = createBaseUpdateUserResponse(); + message.user = (object.user !== undefined && object.user !== null) ? User.fromPartial(object.user) : undefined; + return message; + }, +}; + +function createBaseDeleteUserRequest(): DeleteUserRequest { + return { id: "" }; +} + +export const DeleteUserRequest: MessageFns = { + encode(message: DeleteUserRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.id !== undefined && message.id !== "") { + writer.uint32(10).string(message.id); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): DeleteUserRequest { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseDeleteUserRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.id = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): DeleteUserRequest { + return { id: isSet(object.id) ? globalThis.String(object.id) : "" }; + }, + + toJSON(message: DeleteUserRequest): unknown { + const obj: any = {}; + if (message.id !== undefined && message.id !== "") { + obj.id = message.id; + } + return obj; + }, + + create, I>>(base?: I): DeleteUserRequest { + return DeleteUserRequest.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): DeleteUserRequest { + const message = createBaseDeleteUserRequest(); + message.id = object.id ?? ""; + return message; + }, +}; + +function createBaseDeleteUserResponse(): DeleteUserResponse { + return { success: false }; +} + +export const DeleteUserResponse: MessageFns = { + encode(message: DeleteUserResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.success !== undefined && message.success !== false) { + writer.uint32(8).bool(message.success); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): DeleteUserResponse { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseDeleteUserResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.success = reader.bool(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): DeleteUserResponse { + return { success: isSet(object.success) ? globalThis.Boolean(object.success) : false }; + }, + + toJSON(message: DeleteUserResponse): unknown { + const obj: any = {}; + if (message.success !== undefined && message.success !== false) { + obj.success = message.success; + } + return obj; + }, + + create, I>>(base?: I): DeleteUserResponse { + return DeleteUserResponse.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): DeleteUserResponse { + const message = createBaseDeleteUserResponse(); + message.success = object.success ?? false; + return message; + }, +}; + +function createBaseGetPreferencesRequest(): GetPreferencesRequest { + return { userId: "" }; +} + +export const GetPreferencesRequest: MessageFns = { + encode(message: GetPreferencesRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.userId !== undefined && message.userId !== "") { + writer.uint32(10).string(message.userId); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): GetPreferencesRequest { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGetPreferencesRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.userId = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): GetPreferencesRequest { + return { + userId: isSet(object.userId) + ? globalThis.String(object.userId) + : isSet(object.user_id) + ? globalThis.String(object.user_id) + : "", + }; + }, + + toJSON(message: GetPreferencesRequest): unknown { + const obj: any = {}; + if (message.userId !== undefined && message.userId !== "") { + obj.userId = message.userId; + } + return obj; + }, + + create, I>>(base?: I): GetPreferencesRequest { + return GetPreferencesRequest.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GetPreferencesRequest { + const message = createBaseGetPreferencesRequest(); + message.userId = object.userId ?? ""; + return message; + }, +}; + +function createBaseGetPreferencesResponse(): GetPreferencesResponse { + return { preferences: undefined }; +} + +export const GetPreferencesResponse: MessageFns = { + encode(message: GetPreferencesResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.preferences !== undefined) { + Preferences.encode(message.preferences, writer.uint32(10).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): GetPreferencesResponse { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGetPreferencesResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.preferences = Preferences.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): GetPreferencesResponse { + return { preferences: isSet(object.preferences) ? Preferences.fromJSON(object.preferences) : undefined }; + }, + + toJSON(message: GetPreferencesResponse): unknown { + const obj: any = {}; + if (message.preferences !== undefined) { + obj.preferences = Preferences.toJSON(message.preferences); + } + return obj; + }, + + create, I>>(base?: I): GetPreferencesResponse { + return GetPreferencesResponse.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GetPreferencesResponse { + const message = createBaseGetPreferencesResponse(); + message.preferences = (object.preferences !== undefined && object.preferences !== null) + ? Preferences.fromPartial(object.preferences) + : undefined; + return message; + }, +}; + +function createBaseUpsertPreferencesRequest(): UpsertPreferencesRequest { + return { preferences: undefined }; +} + +export const UpsertPreferencesRequest: MessageFns = { + encode(message: UpsertPreferencesRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.preferences !== undefined) { + Preferences.encode(message.preferences, writer.uint32(10).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): UpsertPreferencesRequest { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseUpsertPreferencesRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.preferences = Preferences.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): UpsertPreferencesRequest { + return { preferences: isSet(object.preferences) ? Preferences.fromJSON(object.preferences) : undefined }; + }, + + toJSON(message: UpsertPreferencesRequest): unknown { + const obj: any = {}; + if (message.preferences !== undefined) { + obj.preferences = Preferences.toJSON(message.preferences); + } + return obj; + }, + + create, I>>(base?: I): UpsertPreferencesRequest { + return UpsertPreferencesRequest.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): UpsertPreferencesRequest { + const message = createBaseUpsertPreferencesRequest(); + message.preferences = (object.preferences !== undefined && object.preferences !== null) + ? Preferences.fromPartial(object.preferences) + : undefined; + return message; + }, +}; + +function createBaseUpsertPreferencesResponse(): UpsertPreferencesResponse { + return { preferences: undefined }; +} + +export const UpsertPreferencesResponse: MessageFns = { + encode(message: UpsertPreferencesResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.preferences !== undefined) { + Preferences.encode(message.preferences, writer.uint32(10).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): UpsertPreferencesResponse { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseUpsertPreferencesResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.preferences = Preferences.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): UpsertPreferencesResponse { + return { preferences: isSet(object.preferences) ? Preferences.fromJSON(object.preferences) : undefined }; + }, + + toJSON(message: UpsertPreferencesResponse): unknown { + const obj: any = {}; + if (message.preferences !== undefined) { + obj.preferences = Preferences.toJSON(message.preferences); + } + return obj; + }, + + create, I>>(base?: I): UpsertPreferencesResponse { + return UpsertPreferencesResponse.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): UpsertPreferencesResponse { + const message = createBaseUpsertPreferencesResponse(); + message.preferences = (object.preferences !== undefined && object.preferences !== null) + ? Preferences.fromPartial(object.preferences) + : undefined; + return message; + }, +}; + +function createBaseUser(): User { + return { + id: "", + email: "", + password: "", + username: undefined, + avatar: undefined, + role: undefined, + googleId: undefined, + storageUsed: 0, + planId: undefined, + createdAt: undefined, + updatedAt: undefined, + }; +} + +export const User: MessageFns = { + encode(message: User, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.id !== undefined && message.id !== "") { + writer.uint32(10).string(message.id); + } + if (message.email !== undefined && message.email !== "") { + writer.uint32(18).string(message.email); + } + if (message.password !== undefined && message.password !== "") { + writer.uint32(26).string(message.password); + } + if (message.username !== undefined) { + writer.uint32(34).string(message.username); + } + if (message.avatar !== undefined) { + writer.uint32(42).string(message.avatar); + } + if (message.role !== undefined) { + writer.uint32(50).string(message.role); + } + if (message.googleId !== undefined) { + writer.uint32(58).string(message.googleId); + } + if (message.storageUsed !== undefined && message.storageUsed !== 0) { + writer.uint32(64).int64(message.storageUsed); + } + if (message.planId !== undefined) { + writer.uint32(74).string(message.planId); + } + if (message.createdAt !== undefined) { + Timestamp.encode(toTimestamp(message.createdAt), writer.uint32(82).fork()).join(); + } + if (message.updatedAt !== undefined) { + Timestamp.encode(toTimestamp(message.updatedAt), writer.uint32(90).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): User { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseUser(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.id = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.email = reader.string(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.password = reader.string(); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.username = reader.string(); + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.avatar = reader.string(); + continue; + } + case 6: { + if (tag !== 50) { + break; + } + + message.role = reader.string(); + continue; + } + case 7: { + if (tag !== 58) { + break; + } + + message.googleId = reader.string(); + continue; + } + case 8: { + if (tag !== 64) { + break; + } + + message.storageUsed = longToNumber(reader.int64()); + continue; + } + case 9: { + if (tag !== 74) { + break; + } + + message.planId = reader.string(); + continue; + } + case 10: { + if (tag !== 82) { + break; + } + + message.createdAt = fromTimestamp(Timestamp.decode(reader, reader.uint32())); + continue; + } + case 11: { + if (tag !== 90) { + break; + } + + message.updatedAt = fromTimestamp(Timestamp.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): User { + return { + id: isSet(object.id) ? globalThis.String(object.id) : "", + email: isSet(object.email) ? globalThis.String(object.email) : "", + password: isSet(object.password) ? globalThis.String(object.password) : "", + username: isSet(object.username) ? globalThis.String(object.username) : undefined, + avatar: isSet(object.avatar) ? globalThis.String(object.avatar) : undefined, + role: isSet(object.role) ? globalThis.String(object.role) : undefined, + googleId: isSet(object.googleId) + ? globalThis.String(object.googleId) + : isSet(object.google_id) + ? globalThis.String(object.google_id) + : undefined, + storageUsed: isSet(object.storageUsed) + ? globalThis.Number(object.storageUsed) + : isSet(object.storage_used) + ? globalThis.Number(object.storage_used) + : 0, + planId: isSet(object.planId) + ? globalThis.String(object.planId) + : isSet(object.plan_id) + ? globalThis.String(object.plan_id) + : undefined, + createdAt: isSet(object.createdAt) + ? globalThis.String(object.createdAt) + : isSet(object.created_at) + ? globalThis.String(object.created_at) + : undefined, + updatedAt: isSet(object.updatedAt) + ? globalThis.String(object.updatedAt) + : isSet(object.updated_at) + ? globalThis.String(object.updated_at) + : undefined, + }; + }, + + toJSON(message: User): unknown { + const obj: any = {}; + if (message.id !== undefined && message.id !== "") { + obj.id = message.id; + } + if (message.email !== undefined && message.email !== "") { + obj.email = message.email; + } + if (message.password !== undefined && message.password !== "") { + obj.password = message.password; + } + if (message.username !== undefined) { + obj.username = message.username; + } + if (message.avatar !== undefined) { + obj.avatar = message.avatar; + } + if (message.role !== undefined) { + obj.role = message.role; + } + if (message.googleId !== undefined) { + obj.googleId = message.googleId; + } + if (message.storageUsed !== undefined && message.storageUsed !== 0) { + obj.storageUsed = Math.round(message.storageUsed); + } + if (message.planId !== undefined) { + obj.planId = message.planId; + } + if (message.createdAt !== undefined) { + obj.createdAt = message.createdAt; + } + if (message.updatedAt !== undefined) { + obj.updatedAt = message.updatedAt; + } + return obj; + }, + + create, I>>(base?: I): User { + return User.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): User { + const message = createBaseUser(); + message.id = object.id ?? ""; + message.email = object.email ?? ""; + message.password = object.password ?? ""; + message.username = object.username ?? undefined; + message.avatar = object.avatar ?? undefined; + message.role = object.role ?? undefined; + message.googleId = object.googleId ?? undefined; + message.storageUsed = object.storageUsed ?? 0; + message.planId = object.planId ?? undefined; + message.createdAt = object.createdAt ?? undefined; + message.updatedAt = object.updatedAt ?? undefined; + return message; + }, +}; + +function createBasePreferences(): Preferences { + return { + userId: "", + language: undefined, + locale: undefined, + emailNotifications: undefined, + pushNotifications: undefined, + marketingNotifications: undefined, + telegramNotifications: undefined, + autoplay: undefined, + loop: undefined, + muted: undefined, + showControls: undefined, + pip: undefined, + airplay: undefined, + chromecast: undefined, + encrytionM3u8: undefined, + }; +} + +export const Preferences: MessageFns = { + encode(message: Preferences, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.userId !== undefined && message.userId !== "") { + writer.uint32(10).string(message.userId); + } + if (message.language !== undefined) { + writer.uint32(18).string(message.language); + } + if (message.locale !== undefined) { + writer.uint32(26).string(message.locale); + } + if (message.emailNotifications !== undefined) { + writer.uint32(32).bool(message.emailNotifications); + } + if (message.pushNotifications !== undefined) { + writer.uint32(40).bool(message.pushNotifications); + } + if (message.marketingNotifications !== undefined) { + writer.uint32(48).bool(message.marketingNotifications); + } + if (message.telegramNotifications !== undefined) { + writer.uint32(56).bool(message.telegramNotifications); + } + if (message.autoplay !== undefined) { + writer.uint32(64).bool(message.autoplay); + } + if (message.loop !== undefined) { + writer.uint32(72).bool(message.loop); + } + if (message.muted !== undefined) { + writer.uint32(80).bool(message.muted); + } + if (message.showControls !== undefined) { + writer.uint32(88).bool(message.showControls); + } + if (message.pip !== undefined) { + writer.uint32(96).bool(message.pip); + } + if (message.airplay !== undefined) { + writer.uint32(104).bool(message.airplay); + } + if (message.chromecast !== undefined) { + writer.uint32(112).bool(message.chromecast); + } + if (message.encrytionM3u8 !== undefined) { + writer.uint32(120).bool(message.encrytionM3u8); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Preferences { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBasePreferences(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.userId = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.language = reader.string(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.locale = reader.string(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.emailNotifications = reader.bool(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.pushNotifications = reader.bool(); + continue; + } + case 6: { + if (tag !== 48) { + break; + } + + message.marketingNotifications = reader.bool(); + continue; + } + case 7: { + if (tag !== 56) { + break; + } + + message.telegramNotifications = reader.bool(); + continue; + } + case 8: { + if (tag !== 64) { + break; + } + + message.autoplay = reader.bool(); + continue; + } + case 9: { + if (tag !== 72) { + break; + } + + message.loop = reader.bool(); + continue; + } + case 10: { + if (tag !== 80) { + break; + } + + message.muted = reader.bool(); + continue; + } + case 11: { + if (tag !== 88) { + break; + } + + message.showControls = reader.bool(); + continue; + } + case 12: { + if (tag !== 96) { + break; + } + + message.pip = reader.bool(); + continue; + } + case 13: { + if (tag !== 104) { + break; + } + + message.airplay = reader.bool(); + continue; + } + case 14: { + if (tag !== 112) { + break; + } + + message.chromecast = reader.bool(); + continue; + } + case 15: { + if (tag !== 120) { + break; + } + + message.encrytionM3u8 = reader.bool(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Preferences { + return { + userId: isSet(object.userId) + ? globalThis.String(object.userId) + : isSet(object.user_id) + ? globalThis.String(object.user_id) + : "", + language: isSet(object.language) ? globalThis.String(object.language) : undefined, + locale: isSet(object.locale) ? globalThis.String(object.locale) : undefined, + emailNotifications: isSet(object.emailNotifications) + ? globalThis.Boolean(object.emailNotifications) + : isSet(object.email_notifications) + ? globalThis.Boolean(object.email_notifications) + : undefined, + pushNotifications: isSet(object.pushNotifications) + ? globalThis.Boolean(object.pushNotifications) + : isSet(object.push_notifications) + ? globalThis.Boolean(object.push_notifications) + : undefined, + marketingNotifications: isSet(object.marketingNotifications) + ? globalThis.Boolean(object.marketingNotifications) + : isSet(object.marketing_notifications) + ? globalThis.Boolean(object.marketing_notifications) + : undefined, + telegramNotifications: isSet(object.telegramNotifications) + ? globalThis.Boolean(object.telegramNotifications) + : isSet(object.telegram_notifications) + ? globalThis.Boolean(object.telegram_notifications) + : undefined, + autoplay: isSet(object.autoplay) ? globalThis.Boolean(object.autoplay) : undefined, + loop: isSet(object.loop) ? globalThis.Boolean(object.loop) : undefined, + muted: isSet(object.muted) ? globalThis.Boolean(object.muted) : undefined, + showControls: isSet(object.showControls) + ? globalThis.Boolean(object.showControls) + : isSet(object.show_controls) + ? globalThis.Boolean(object.show_controls) + : undefined, + pip: isSet(object.pip) ? globalThis.Boolean(object.pip) : undefined, + airplay: isSet(object.airplay) ? globalThis.Boolean(object.airplay) : undefined, + chromecast: isSet(object.chromecast) ? globalThis.Boolean(object.chromecast) : undefined, + encrytionM3u8: isSet(object.encrytionM3u8) + ? globalThis.Boolean(object.encrytionM3u8) + : isSet(object.encrytion_m3u8) + ? globalThis.Boolean(object.encrytion_m3u8) + : undefined, + }; + }, + + toJSON(message: Preferences): unknown { + const obj: any = {}; + if (message.userId !== undefined && message.userId !== "") { + obj.userId = message.userId; + } + if (message.language !== undefined) { + obj.language = message.language; + } + if (message.locale !== undefined) { + obj.locale = message.locale; + } + if (message.emailNotifications !== undefined) { + obj.emailNotifications = message.emailNotifications; + } + if (message.pushNotifications !== undefined) { + obj.pushNotifications = message.pushNotifications; + } + if (message.marketingNotifications !== undefined) { + obj.marketingNotifications = message.marketingNotifications; + } + if (message.telegramNotifications !== undefined) { + obj.telegramNotifications = message.telegramNotifications; + } + if (message.autoplay !== undefined) { + obj.autoplay = message.autoplay; + } + if (message.loop !== undefined) { + obj.loop = message.loop; + } + if (message.muted !== undefined) { + obj.muted = message.muted; + } + if (message.showControls !== undefined) { + obj.showControls = message.showControls; + } + if (message.pip !== undefined) { + obj.pip = message.pip; + } + if (message.airplay !== undefined) { + obj.airplay = message.airplay; + } + if (message.chromecast !== undefined) { + obj.chromecast = message.chromecast; + } + if (message.encrytionM3u8 !== undefined) { + obj.encrytionM3u8 = message.encrytionM3u8; + } + return obj; + }, + + create, I>>(base?: I): Preferences { + return Preferences.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Preferences { + const message = createBasePreferences(); + message.userId = object.userId ?? ""; + message.language = object.language ?? undefined; + message.locale = object.locale ?? undefined; + message.emailNotifications = object.emailNotifications ?? undefined; + message.pushNotifications = object.pushNotifications ?? undefined; + message.marketingNotifications = object.marketingNotifications ?? undefined; + message.telegramNotifications = object.telegramNotifications ?? undefined; + message.autoplay = object.autoplay ?? undefined; + message.loop = object.loop ?? undefined; + message.muted = object.muted ?? undefined; + message.showControls = object.showControls ?? undefined; + message.pip = object.pip ?? undefined; + message.airplay = object.airplay ?? undefined; + message.chromecast = object.chromecast ?? undefined; + message.encrytionM3u8 = object.encrytionM3u8 ?? undefined; + return message; + }, +}; + +export type UserServiceService = typeof UserServiceService; +export const UserServiceService = { + /** User CRUD */ + getUser: { + path: "/stream.User.v1.UserService/GetUser", + requestStream: false, + responseStream: false, + requestSerialize: (value: GetUserRequest): Buffer => Buffer.from(GetUserRequest.encode(value).finish()), + requestDeserialize: (value: Buffer): GetUserRequest => GetUserRequest.decode(value), + responseSerialize: (value: GetUserResponse): Buffer => Buffer.from(GetUserResponse.encode(value).finish()), + responseDeserialize: (value: Buffer): GetUserResponse => GetUserResponse.decode(value), + }, + getUserByEmail: { + path: "/stream.User.v1.UserService/GetUserByEmail", + requestStream: false, + responseStream: false, + requestSerialize: (value: GetUserByEmailRequest): Buffer => + Buffer.from(GetUserByEmailRequest.encode(value).finish()), + requestDeserialize: (value: Buffer): GetUserByEmailRequest => GetUserByEmailRequest.decode(value), + responseSerialize: (value: GetUserResponse): Buffer => Buffer.from(GetUserResponse.encode(value).finish()), + responseDeserialize: (value: Buffer): GetUserResponse => GetUserResponse.decode(value), + }, + listUsers: { + path: "/stream.User.v1.UserService/ListUsers", + requestStream: false, + responseStream: false, + requestSerialize: (value: ListUsersRequest): Buffer => Buffer.from(ListUsersRequest.encode(value).finish()), + requestDeserialize: (value: Buffer): ListUsersRequest => ListUsersRequest.decode(value), + responseSerialize: (value: ListUsersResponse): Buffer => Buffer.from(ListUsersResponse.encode(value).finish()), + responseDeserialize: (value: Buffer): ListUsersResponse => ListUsersResponse.decode(value), + }, + createUser: { + path: "/stream.User.v1.UserService/CreateUser", + requestStream: false, + responseStream: false, + requestSerialize: (value: CreateUserRequest): Buffer => Buffer.from(CreateUserRequest.encode(value).finish()), + requestDeserialize: (value: Buffer): CreateUserRequest => CreateUserRequest.decode(value), + responseSerialize: (value: CreateUserResponse): Buffer => Buffer.from(CreateUserResponse.encode(value).finish()), + responseDeserialize: (value: Buffer): CreateUserResponse => CreateUserResponse.decode(value), + }, + updateUser: { + path: "/stream.User.v1.UserService/UpdateUser", + requestStream: false, + responseStream: false, + requestSerialize: (value: UpdateUserRequest): Buffer => Buffer.from(UpdateUserRequest.encode(value).finish()), + requestDeserialize: (value: Buffer): UpdateUserRequest => UpdateUserRequest.decode(value), + responseSerialize: (value: UpdateUserResponse): Buffer => Buffer.from(UpdateUserResponse.encode(value).finish()), + responseDeserialize: (value: Buffer): UpdateUserResponse => UpdateUserResponse.decode(value), + }, + deleteUser: { + path: "/stream.User.v1.UserService/DeleteUser", + requestStream: false, + responseStream: false, + requestSerialize: (value: DeleteUserRequest): Buffer => Buffer.from(DeleteUserRequest.encode(value).finish()), + requestDeserialize: (value: Buffer): DeleteUserRequest => DeleteUserRequest.decode(value), + responseSerialize: (value: DeleteUserResponse): Buffer => Buffer.from(DeleteUserResponse.encode(value).finish()), + responseDeserialize: (value: Buffer): DeleteUserResponse => DeleteUserResponse.decode(value), + }, + /** Preferences */ + getPreferences: { + path: "/stream.User.v1.UserService/GetPreferences", + requestStream: false, + responseStream: false, + requestSerialize: (value: GetPreferencesRequest): Buffer => + Buffer.from(GetPreferencesRequest.encode(value).finish()), + requestDeserialize: (value: Buffer): GetPreferencesRequest => GetPreferencesRequest.decode(value), + responseSerialize: (value: GetPreferencesResponse): Buffer => + Buffer.from(GetPreferencesResponse.encode(value).finish()), + responseDeserialize: (value: Buffer): GetPreferencesResponse => GetPreferencesResponse.decode(value), + }, + upsertPreferences: { + path: "/stream.User.v1.UserService/UpsertPreferences", + requestStream: false, + responseStream: false, + requestSerialize: (value: UpsertPreferencesRequest): Buffer => + Buffer.from(UpsertPreferencesRequest.encode(value).finish()), + requestDeserialize: (value: Buffer): UpsertPreferencesRequest => UpsertPreferencesRequest.decode(value), + responseSerialize: (value: UpsertPreferencesResponse): Buffer => + Buffer.from(UpsertPreferencesResponse.encode(value).finish()), + responseDeserialize: (value: Buffer): UpsertPreferencesResponse => UpsertPreferencesResponse.decode(value), + }, +} as const; + +export interface UserServiceServer extends UntypedServiceImplementation { + /** User CRUD */ + getUser: handleUnaryCall; + getUserByEmail: handleUnaryCall; + listUsers: handleUnaryCall; + createUser: handleUnaryCall; + updateUser: handleUnaryCall; + deleteUser: handleUnaryCall; + /** Preferences */ + getPreferences: handleUnaryCall; + upsertPreferences: handleUnaryCall; +} + +export interface UserServiceClient extends Client { + /** User CRUD */ + getUser( + request: GetUserRequest, + callback: (error: ServiceError | null, response: GetUserResponse) => void, + ): ClientUnaryCall; + getUser( + request: GetUserRequest, + metadata: Metadata, + callback: (error: ServiceError | null, response: GetUserResponse) => void, + ): ClientUnaryCall; + getUser( + request: GetUserRequest, + metadata: Metadata, + options: Partial, + callback: (error: ServiceError | null, response: GetUserResponse) => void, + ): ClientUnaryCall; + getUserByEmail( + request: GetUserByEmailRequest, + callback: (error: ServiceError | null, response: GetUserResponse) => void, + ): ClientUnaryCall; + getUserByEmail( + request: GetUserByEmailRequest, + metadata: Metadata, + callback: (error: ServiceError | null, response: GetUserResponse) => void, + ): ClientUnaryCall; + getUserByEmail( + request: GetUserByEmailRequest, + metadata: Metadata, + options: Partial, + callback: (error: ServiceError | null, response: GetUserResponse) => void, + ): ClientUnaryCall; + listUsers( + request: ListUsersRequest, + callback: (error: ServiceError | null, response: ListUsersResponse) => void, + ): ClientUnaryCall; + listUsers( + request: ListUsersRequest, + metadata: Metadata, + callback: (error: ServiceError | null, response: ListUsersResponse) => void, + ): ClientUnaryCall; + listUsers( + request: ListUsersRequest, + metadata: Metadata, + options: Partial, + callback: (error: ServiceError | null, response: ListUsersResponse) => void, + ): ClientUnaryCall; + createUser( + request: CreateUserRequest, + callback: (error: ServiceError | null, response: CreateUserResponse) => void, + ): ClientUnaryCall; + createUser( + request: CreateUserRequest, + metadata: Metadata, + callback: (error: ServiceError | null, response: CreateUserResponse) => void, + ): ClientUnaryCall; + createUser( + request: CreateUserRequest, + metadata: Metadata, + options: Partial, + callback: (error: ServiceError | null, response: CreateUserResponse) => void, + ): ClientUnaryCall; + updateUser( + request: UpdateUserRequest, + callback: (error: ServiceError | null, response: UpdateUserResponse) => void, + ): ClientUnaryCall; + updateUser( + request: UpdateUserRequest, + metadata: Metadata, + callback: (error: ServiceError | null, response: UpdateUserResponse) => void, + ): ClientUnaryCall; + updateUser( + request: UpdateUserRequest, + metadata: Metadata, + options: Partial, + callback: (error: ServiceError | null, response: UpdateUserResponse) => void, + ): ClientUnaryCall; + deleteUser( + request: DeleteUserRequest, + callback: (error: ServiceError | null, response: DeleteUserResponse) => void, + ): ClientUnaryCall; + deleteUser( + request: DeleteUserRequest, + metadata: Metadata, + callback: (error: ServiceError | null, response: DeleteUserResponse) => void, + ): ClientUnaryCall; + deleteUser( + request: DeleteUserRequest, + metadata: Metadata, + options: Partial, + callback: (error: ServiceError | null, response: DeleteUserResponse) => void, + ): ClientUnaryCall; + /** Preferences */ + getPreferences( + request: GetPreferencesRequest, + callback: (error: ServiceError | null, response: GetPreferencesResponse) => void, + ): ClientUnaryCall; + getPreferences( + request: GetPreferencesRequest, + metadata: Metadata, + callback: (error: ServiceError | null, response: GetPreferencesResponse) => void, + ): ClientUnaryCall; + getPreferences( + request: GetPreferencesRequest, + metadata: Metadata, + options: Partial, + callback: (error: ServiceError | null, response: GetPreferencesResponse) => void, + ): ClientUnaryCall; + upsertPreferences( + request: UpsertPreferencesRequest, + callback: (error: ServiceError | null, response: UpsertPreferencesResponse) => void, + ): ClientUnaryCall; + upsertPreferences( + request: UpsertPreferencesRequest, + metadata: Metadata, + callback: (error: ServiceError | null, response: UpsertPreferencesResponse) => void, + ): ClientUnaryCall; + upsertPreferences( + request: UpsertPreferencesRequest, + metadata: Metadata, + options: Partial, + callback: (error: ServiceError | null, response: UpsertPreferencesResponse) => void, + ): ClientUnaryCall; +} + +export const UserServiceClient = makeGenericClientConstructor( + UserServiceService, + "stream.User.v1.UserService", +) as unknown as { + new (address: string, credentials: ChannelCredentials, options?: Partial): UserServiceClient; + service: typeof UserServiceService; + serviceName: string; +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.Array> + : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function toTimestamp(dateStr: string): Timestamp { + const date = new globalThis.Date(dateStr); + const seconds = Math.trunc(date.getTime() / 1_000); + const nanos = (date.getTime() % 1_000) * 1_000_000; + return { seconds, nanos }; +} + +function fromTimestamp(t: Timestamp): string { + let millis = (t.seconds || 0) * 1_000; + millis += (t.nanos || 0) / 1_000_000; + return new globalThis.Date(millis).toISOString(); +} + +function longToNumber(int64: { toString(): string }): number { + const num = globalThis.Number(int64.toString()); + if (num > globalThis.Number.MAX_SAFE_INTEGER) { + throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER"); + } + if (num < globalThis.Number.MIN_SAFE_INTEGER) { + throw new globalThis.Error("Value is smaller than Number.MIN_SAFE_INTEGER"); + } + return num; +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} + +export interface MessageFns { + encode(message: T, writer?: BinaryWriter): BinaryWriter; + decode(input: BinaryReader | Uint8Array, length?: number): T; + fromJSON(object: any): T; + toJSON(message: T): unknown; + create, I>>(base?: I): T; + fromPartial, I>>(object: I): T; +} diff --git a/src/server/utils/token.ts b/src/server/utils/token.ts new file mode 100644 index 0000000..4ba027f --- /dev/null +++ b/src/server/utils/token.ts @@ -0,0 +1,111 @@ +import { randomUUID } from "crypto" +import { sign, verify } from "hono/jwt" +import { JWTPayload } from "hono/utils/jwt/types" + +export interface Provider { + generateTokenPair( + userID: string, + email: string, + role: string + ): Promise + + parseToken(token: string): Promise + + parseMapToken(token: string): Promise> +} + +export interface TokenPair { + accessToken: string + refreshToken: string + atExpires: number + rtExpires: number + accessUUID: string + refreshUUID: string +} + +export interface Claims { + userID: string + email: string + role: string + tokenID: string +} + +interface JwtClaims { + user_id: string + email: string + role: string + token_id: string + iss: string + exp: number +} + +export class JwtProvider implements Provider { + constructor(private secret: string) {} + + static newJWTProvider(secret: string): Provider { + return new JwtProvider(secret) + } + + async generateTokenPair( + userID: string, + email: string, + role: string + ): Promise { + const now = Math.floor(Date.now() / 1000) + + const td: TokenPair = { + accessToken: "", + refreshToken: "", + atExpires: now + 15 * 60, + rtExpires: now + 7 * 24 * 60 * 60, + accessUUID: randomUUID(), + refreshUUID: randomUUID(), + } + + // ACCESS TOKEN + const accessPayload: JWTPayload = { + user_id: userID, + email, + role, + token_id: td.accessUUID, + iss: "stream.api", + exp: td.atExpires, + } + + td.accessToken = await sign(accessPayload, this.secret) + + // REFRESH TOKEN + const refreshPayload = { + refresh_uuid: td.refreshUUID, + user_id: userID, + exp: td.rtExpires, + } + + td.refreshToken = await sign(refreshPayload, this.secret) + + return td + } + + async parseToken(token: string): Promise { + const payload = (await verify(token, this.secret, "HS256")) + + if (!payload) { + throw new Error("invalid token") + } + return payload + } + + async parseMapToken(token: string): Promise { + const payload = await verify(token, this.secret, "HS256") + + if (!payload) { + throw new Error("invalid token") + } + + return payload + } +} + +export function JWTProvider(secret: string): Provider { + return new JwtProvider(secret) +} \ No newline at end of file diff --git a/wrangler.jsonc b/wrangler.jsonc deleted file mode 100644 index bd16eed..0000000 --- a/wrangler.jsonc +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "node_modules/wrangler/config-schema.json", - "name": "holistream", - "compatibility_date": "2025-08-03", - "main": "./src/index.tsx", - "compatibility_flags": [ - "nodejs_compat" - ], - "observability": { - "enabled": true, - "head_sampling_rate": 1, - "logs": { - "enabled": true, - "head_sampling_rate": 1, - "persist": true, - "invocation_logs": true - }, - "traces": { - "enabled": true, - "persist": true, - "head_sampling_rate": 1 - } - } -} \ No newline at end of file