Merge pull request #5 from k-puig/feature/full-endpoint

Feature/full endpoint
This commit is contained in:
Kevin Puig
2025-09-27 23:22:32 -04:00
committed by GitHub
37 changed files with 3644 additions and 49 deletions

View File

@@ -0,0 +1,10 @@
# EditorConfig helps maintain consistent coding styles
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View File

@@ -1,2 +1,3 @@
# deps # deps
node_modules/ node_modules/
generated/

View File

@@ -1,9 +1,11 @@
To install dependencies: To install dependencies:
```sh ```sh
bun install bun install
``` ```
To run: To run:
```sh ```sh
bun run dev bun run dev
``` ```

View File

@@ -4,93 +4,240 @@
"": { "": {
"name": "concord-server", "name": "concord-server",
"dependencies": { "dependencies": {
"@hono/standard-validator": "^0.1.5",
"@hono/zod-validator": "^0.7.3",
"@prisma/client": "^6.16.2",
"@scalar/hono-api-reference": "^0.9.19",
"@socket.io/bun-engine": "^0.0.3",
"hono": "^4.9.9", "hono": "^4.9.9",
"hono-openapi": "^1.1.0",
"prisma": "^6.16.2", "prisma": "^6.16.2",
"socket.io": "^4.8.1",
"zod": "^4.1.11",
}, },
"devDependencies": { "devDependencies": {
"@types/bun": "latest", "@types/bun": "latest",
"prisma-zod-generator": "^1.22.1",
}, },
}, },
}, },
"packages": { "packages": {
"@prisma/config": ["@prisma/config@6.16.2", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.16.12", "empathic": "2.0.0" } }, "sha512-mKXSUrcqXj0LXWPmJsK2s3p9PN+aoAbyMx7m5E1v1FufofR1ZpPoIArjjzOIm+bJRLLvYftoNYLx1tbHgF9/yg=="], "@hono/standard-validator": ["@hono/standard-validator@0.1.5", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-EIyZPPwkyLn6XKwFj5NBEWHXhXbgmnVh2ceIFo5GO7gKI9WmzTjPDKnppQB0KrqKeAkq3kpoW4SIbu5X1dgx3w=="],
"@prisma/debug": ["@prisma/debug@6.16.2", "", {}, "sha512-bo4/gA/HVV6u8YK2uY6glhNsJ7r+k/i5iQ9ny/3q5bt9ijCj7WMPUwfTKPvtEgLP+/r26Z686ly11hhcLiQ8zA=="], "@hono/zod-validator": ["@hono/zod-validator@0.7.3", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-uYGdgVib3RlGD698WR5dVM0zB3UuPY5vHKXffGUbUh7r4xY+mFIhF3/v4AcQVLrU5CQdBso8BJr4wuVoCrjTuQ=="],
"@prisma/engines": ["@prisma/engines@6.16.2", "", { "dependencies": { "@prisma/debug": "6.16.2", "@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "@prisma/fetch-engine": "6.16.2", "@prisma/get-platform": "6.16.2" } }, "sha512-7yf3AjfPUgsg/l7JSu1iEhsmZZ/YE00yURPjTikqm2z4btM0bCl2coFtTGfeSOWbQMmq45Jab+53yGUIAT1sjA=="], "@prisma/client": ["@prisma/client@6.16.2", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"] }, "sha512-E00PxBcalMfYO/TWnXobBVUai6eW/g5OsifWQsQDzJYm7yaY+IRLo7ZLsaefi0QkTpxfuhFcQ/w180i6kX3iJw=="],
"@prisma/engines-version": ["@prisma/engines-version@6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "", {}, "sha512-ThvlDaKIVrnrv97ujNFDYiQbeMQpLa0O86HFA2mNoip4mtFqM7U5GSz2ie1i2xByZtvPztJlNRgPsXGeM/kqAA=="], "@prisma/config": ["@prisma/config@6.16.2", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.16.12", "empathic": "2.0.0" } }, ""],
"@prisma/fetch-engine": ["@prisma/fetch-engine@6.16.2", "", { "dependencies": { "@prisma/debug": "6.16.2", "@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "@prisma/get-platform": "6.16.2" } }, "sha512-wPnZ8DMRqpgzye758ZvfAMiNJRuYpz+rhgEBZi60ZqDIgOU2694oJxiuu3GKFeYeR/hXxso4/2oBC243t/whxQ=="], "@prisma/debug": ["@prisma/debug@6.16.2", "", {}, ""],
"@prisma/get-platform": ["@prisma/get-platform@6.16.2", "", { "dependencies": { "@prisma/debug": "6.16.2" } }, "sha512-U/P36Uke5wS7r1+omtAgJpEB94tlT4SdlgaeTc6HVTTT93pXj7zZ+B/cZnmnvjcNPfWddgoDx8RLjmQwqGDYyA=="], "@prisma/dmmf": ["@prisma/dmmf@6.16.2", "", {}, "sha512-o9ztgdbj2KZXl6DL+oP56TTC0poTLPns9/MeU761b49E1IQ/fd0jwdov1bidlNOiwio8Nsou23xNrYE/db10aA=="],
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], "@prisma/driver-adapter-utils": ["@prisma/driver-adapter-utils@6.16.2", "", { "dependencies": { "@prisma/debug": "6.16.2" } }, "sha512-DMgfafnG0zPd+QoAQOC0Trn1xlb0fVAfQi2MpkpzSf641KiVkVPkJRXDSbcTbxGxO2HRdd0vI9U6LlesWad4XA=="],
"@prisma/engines": ["@prisma/engines@6.16.2", "", { "dependencies": { "@prisma/debug": "6.16.2", "@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "@prisma/fetch-engine": "6.16.2", "@prisma/get-platform": "6.16.2" } }, ""],
"@prisma/engines-version": ["@prisma/engines-version@6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "", {}, ""],
"@prisma/fetch-engine": ["@prisma/fetch-engine@6.16.2", "", { "dependencies": { "@prisma/debug": "6.16.2", "@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "@prisma/get-platform": "6.16.2" } }, ""],
"@prisma/generator": ["@prisma/generator@6.16.2", "", {}, "sha512-7bwRmtMIgfe1rUynh1p9VlmYvEiidbRO6aBphPBS6YGEGSvNe8+QExbRpsqFlFBvIX76BhZCxuEj7ZwALMYRKQ=="],
"@prisma/generator-helper": ["@prisma/generator-helper@6.16.2", "", { "dependencies": { "@prisma/debug": "6.16.2", "@prisma/dmmf": "6.16.2", "@prisma/generator": "6.16.2" } }, "sha512-8tVnWM8ETJNrvI5CT9eKCW23+aPLNkidC+g9NJn7ghXm60Q7GGlLX5tmvn5dE8tXvs/FSX3MN7KNmNJpOr89Hw=="],
"@prisma/get-platform": ["@prisma/get-platform@6.16.2", "", { "dependencies": { "@prisma/debug": "6.16.2" } }, ""],
"@prisma/internals": ["@prisma/internals@6.16.2", "", { "dependencies": { "@prisma/config": "6.16.2", "@prisma/debug": "6.16.2", "@prisma/dmmf": "6.16.2", "@prisma/driver-adapter-utils": "6.16.2", "@prisma/engines": "6.16.2", "@prisma/fetch-engine": "6.16.2", "@prisma/generator": "6.16.2", "@prisma/generator-helper": "6.16.2", "@prisma/get-platform": "6.16.2", "@prisma/prisma-schema-wasm": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "@prisma/schema-engine-wasm": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "@prisma/schema-files-loader": "6.16.2", "arg": "5.0.2", "prompts": "2.4.2" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"] }, "sha512-gwmWl7H8iTbi+58RXua5Lsus5LDbIZGO2wQ4RoSX9YtEbKWHwRP8TUzTVLwRNeJ2DHwfnzhTLrUnybwotqiACg=="],
"@prisma/prisma-schema-wasm": ["@prisma/prisma-schema-wasm@6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "", {}, "sha512-DvYi0zKqzPd49Z5japS3FawyMylscaoUmlXNhnRAXb8HZryG4Q7TM1FLX8OIAfCgLmoWS1c/Zf4UZznBXkvWSg=="],
"@prisma/schema-engine-wasm": ["@prisma/schema-engine-wasm@6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "", {}, "sha512-HDgFE0um5OHkk2pkQbAgARR284i2VpoM+7GYRAT0zxoTagsdaZ6yquJF2LEZuAKfibib0Ct7JZxRCB8eN/Ru6g=="],
"@prisma/schema-files-loader": ["@prisma/schema-files-loader@6.16.2", "", { "dependencies": { "@prisma/prisma-schema-wasm": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "fs-extra": "11.3.0" } }, "sha512-TN77DUFgOxT/WL6wuxVhn7qVDvwVRl0TEzhFfRh5vKQsuZ5itLzA7Ki4TgOs4Pk18wwZnti6ZKdzR3Y7cu2KsA=="],
"@scalar/core": ["@scalar/core@0.3.17", "", { "dependencies": { "@scalar/types": "0.2.16" } }, "sha512-G6tP+2oorFA90szI8DGiEQ23SmobiuhN93GfTJNpFMhz/kdEtC4lcYawAxL1tWkZhlK/QYRcaCZcVzmpTUgBCA=="],
"@scalar/hono-api-reference": ["@scalar/hono-api-reference@0.9.19", "", { "dependencies": { "@scalar/core": "0.3.17" }, "peerDependencies": { "hono": "^4.0.0" } }, "sha512-MeBdIwuAAhwvsUt1f9174wASu0C8b4WrXuVdC/6FjrsTxLHSbJVx+XFzZAKF5ex+1kaXjvYrm3fiovRFHLyfJA=="],
"@scalar/openapi-types": ["@scalar/openapi-types@0.3.7", "", { "dependencies": { "zod": "3.24.1" } }, "sha512-QHSvHBVDze3+dUwAhIGq6l1iOev4jdoqdBK7QpfeN1Q4h+6qpVEw3EEqBiH0AXUSh/iWwObBv4uMgfIx0aNZ5g=="],
"@scalar/types": ["@scalar/types@0.2.16", "", { "dependencies": { "@scalar/openapi-types": "0.3.7", "nanoid": "5.1.5", "zod": "3.24.1" } }, "sha512-XWff9jWfYaj6q3ww94x66S6Q58u/3kA1sDOUhLAwb9va7r58bzk3NRwLOkEEdJmyEns1MEJAM53mY8KRWX6elA=="],
"@socket.io/bun-engine": ["@socket.io/bun-engine@0.0.3", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-OK2ZObq9hKsxyAcV7xDTcWGubBmfEY3Lt4nb04K+HlYl9G5PDgrY9hxJm9uV+B0xo3MhKFrgdg9VQsVZ3pbT/g=="],
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
"@standard-community/standard-json": ["@standard-community/standard-json@0.3.5", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "@types/json-schema": "^7.0.15", "@valibot/to-json-schema": "^1.3.0", "arktype": "^2.1.20", "effect": "^3.16.8", "quansync": "^0.2.11", "sury": "^10.0.0", "typebox": "^1.0.17", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-to-json-schema"] }, "sha512-4+ZPorwDRt47i+O7RjyuaxHRK/37QY/LmgxlGrRrSTLYoFatEOzvqIc85GTlM18SFZ5E91C+v0o/M37wZPpUHA=="],
"@standard-community/standard-openapi": ["@standard-community/standard-openapi@0.2.8", "", { "peerDependencies": { "@standard-community/standard-json": "^0.3.5", "@standard-schema/spec": "^1.0.0", "arktype": "^2.1.20", "effect": "^3.17.14", "openapi-types": "^12.1.3", "sury": "^10.0.0", "typebox": "^1.0.0", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-openapi": "^4" }, "optionalPeers": ["arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-openapi"] }, "sha512-80ap74p5oy/SU4al5HkPwO5+NbN2wH/FBr6kwaE5ROq7AvcDFaxzUfTazewroNaCotbvdGcvzXb9oEoOIyfC/Q=="],
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, ""],
"@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="], "@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
"@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="], "@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="],
"@types/react": ["@types/react@19.1.14", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q=="], "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="], "@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, ""],
"c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="], "@types/react": ["@types/react@19.1.14", "", { "dependencies": { "csstype": "^3.0.2" } }, ""],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
"citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
"confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], "base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="],
"deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="], "bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, ""],
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], "c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, ""],
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, ""],
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, ""],
"effect": ["effect@3.16.12", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg=="], "confbox": ["confbox@0.2.2", "", {}, ""],
"empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], "consola": ["consola@3.4.2", "", {}, ""],
"exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="], "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
"giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], "csstype": ["csstype@3.1.3", "", {}, ""],
"hono": ["hono@4.9.9", "", {}, "sha512-Hxw4wT6zjJGZJdkJzAx9PyBdf7ZpxaTSA0NfxqjLghwMrLBX8p33hJBzoETRakF3UJu6OdNQBZAlNSkGqKFukw=="], "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
"jiti": ["jiti@2.6.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ=="], "debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], "deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, ""],
"nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="], "defu": ["defu@6.1.4", "", {}, ""],
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], "destr": ["destr@2.0.5", "", {}, ""],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "dotenv": ["dotenv@16.6.1", "", {}, ""],
"perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], "effect": ["effect@3.16.12", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, ""],
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], "empathic": ["empathic@2.0.0", "", {}, ""],
"prisma": ["prisma@6.16.2", "", { "dependencies": { "@prisma/config": "6.16.2", "@prisma/engines": "6.16.2" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-aRvldGE5UUJTtVmFiH3WfNFNiqFlAtePUxcI0UEGlnXCX7DqhiMT5TRYwncHFeA/Reca5W6ToXXyCMTeFPdSXA=="], "engine.io": ["engine.io@6.6.4", "", { "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", "ws": "~8.17.1" } }, "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g=="],
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], "engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
"rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], "exsolve": ["exsolve@1.0.7", "", {}, ""],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, ""],
"tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="], "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
"fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
"giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": "dist/cli.mjs" }, ""],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"hono": ["hono@4.9.9", "", {}, ""],
"hono-openapi": ["hono-openapi@1.1.0", "", { "peerDependencies": { "@hono/standard-validator": "^0.1.2", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.8", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-eA5hN8D2O30EkPPUxWFilcZcThAe81TShbH38Y183ZZp8WkgMh4BrPEDeZ/EFN2tyDi3cmTgKTa3+oStyJX0UA=="],
"jiti": ["jiti@2.6.0", "", { "bin": "lib/jiti-cli.mjs" }, ""],
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
"kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="],
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
"node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, ""],
"nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": "dist/cli.mjs" }, ""],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"ohash": ["ohash@2.0.11", "", {}, ""],
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
"pathe": ["pathe@2.0.3", "", {}, ""],
"perfect-debounce": ["perfect-debounce@1.0.0", "", {}, ""],
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, ""],
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
"prisma": ["prisma@6.16.2", "", { "dependencies": { "@prisma/config": "6.16.2", "@prisma/engines": "6.16.2" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": "build/index.js" }, ""],
"prisma-zod-generator": ["prisma-zod-generator@1.22.1", "", { "dependencies": { "@prisma/client": "^6.16.2", "@prisma/generator-helper": "^6.16.2", "@prisma/internals": "^6.16.2", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "node-fetch": "^3.3.2", "prettier": "^3.6.2", "tslib": "^2.8.1" }, "peerDependencies": { "zod": ">=3.25.0 <5" }, "bin": { "prisma-zod-generator": "lib/generator.js" } }, "sha512-nBr00sfR8onGCD5eIDLHoFrpeJTSuZxSeaO61Zg6CAEyXPR51gpkO1ev9huG7+tsV+mm8me8VNl8hMcVtWl8FA=="],
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
"pure-rand": ["pure-rand@6.1.0", "", {}, ""],
"quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
"rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, ""],
"readdirp": ["readdirp@4.1.2", "", {}, ""],
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
"socket.io": ["socket.io@4.8.1", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg=="],
"socket.io-adapter": ["socket.io-adapter@2.5.5", "", { "dependencies": { "debug": "~4.3.4", "ws": "~8.17.1" } }, "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg=="],
"socket.io-parser": ["socket.io-parser@4.2.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" } }, "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew=="],
"tinyexec": ["tinyexec@1.0.1", "", {}, ""],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
"undici-types": ["undici-types@7.12.0", "", {}, ""],
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
"ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="],
"zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@scalar/openapi-types/zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="],
"@scalar/types/zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="],
} }
} }

View File

@@ -0,0 +1,131 @@
-- CreateTable
CREATE TABLE "public"."Instance" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"icon" TEXT,
CONSTRAINT "Instance_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "public"."User" (
"id" TEXT NOT NULL,
"username" TEXT NOT NULL,
"nickname" TEXT,
"bio" TEXT,
"picture" TEXT,
"banner" TEXT,
"admin" BOOLEAN NOT NULL,
"status" TEXT NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "public"."Role" (
"userId" TEXT NOT NULL,
"instanceId" TEXT NOT NULL,
"type" TEXT NOT NULL
);
-- CreateTable
CREATE TABLE "public"."UserAuth" (
"userId" TEXT NOT NULL,
"password" TEXT NOT NULL,
"token" TEXT
);
-- CreateTable
CREATE TABLE "public"."Category" (
"id" TEXT NOT NULL,
"instanceId" TEXT,
"name" TEXT NOT NULL,
"position" INTEGER NOT NULL,
CONSTRAINT "Category_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "public"."Channel" (
"id" TEXT NOT NULL,
"type" TEXT NOT NULL,
"categoryId" TEXT,
"name" TEXT NOT NULL,
"description" TEXT NOT NULL,
CONSTRAINT "Channel_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "public"."Message" (
"id" TEXT NOT NULL,
"channelId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"deleted" BOOLEAN NOT NULL,
"text" TEXT NOT NULL,
CONSTRAINT "Message_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "public"."Reply" (
"messageId" TEXT NOT NULL,
"repliesToId" TEXT NOT NULL
);
-- CreateTable
CREATE TABLE "public"."MessagePing" (
"messageId" TEXT NOT NULL,
"pingsUserId" TEXT NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "Role_userId_instanceId_key" ON "public"."Role"("userId", "instanceId");
-- CreateIndex
CREATE UNIQUE INDEX "UserAuth_userId_key" ON "public"."UserAuth"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "Reply_messageId_key" ON "public"."Reply"("messageId");
-- CreateIndex
CREATE UNIQUE INDEX "Reply_repliesToId_key" ON "public"."Reply"("repliesToId");
-- CreateIndex
CREATE UNIQUE INDEX "Reply_messageId_repliesToId_key" ON "public"."Reply"("messageId", "repliesToId");
-- CreateIndex
CREATE UNIQUE INDEX "MessagePing_messageId_pingsUserId_key" ON "public"."MessagePing"("messageId", "pingsUserId");
-- AddForeignKey
ALTER TABLE "public"."Role" ADD CONSTRAINT "Role_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Role" ADD CONSTRAINT "Role_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "public"."Instance"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."UserAuth" ADD CONSTRAINT "UserAuth_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Category" ADD CONSTRAINT "Category_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "public"."Instance"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Channel" ADD CONSTRAINT "Channel_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "public"."Category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Message" ADD CONSTRAINT "Message_channelId_fkey" FOREIGN KEY ("channelId") REFERENCES "public"."Channel"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Message" ADD CONSTRAINT "Message_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Reply" ADD CONSTRAINT "Reply_messageId_fkey" FOREIGN KEY ("messageId") REFERENCES "public"."Message"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Reply" ADD CONSTRAINT "Reply_repliesToId_fkey" FOREIGN KEY ("repliesToId") REFERENCES "public"."Message"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."MessagePing" ADD CONSTRAINT "MessagePing_messageId_fkey" FOREIGN KEY ("messageId") REFERENCES "public"."Message"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."MessagePing" ADD CONSTRAINT "MessagePing_pingsUserId_fkey" FOREIGN KEY ("pingsUserId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,71 @@
/*
Warnings:
- Added the required column `updatedAt` to the `Category` table without a default value. This is not possible if the table is not empty.
- Added the required column `pinnedId` to the `Channel` table without a default value. This is not possible if the table is not empty.
- Added the required column `updatedAt` to the `Channel` table without a default value. This is not possible if the table is not empty.
- Added the required column `updatedAt` to the `Instance` table without a default value. This is not possible if the table is not empty.
- Added the required column `updatedAt` to the `Message` table without a default value. This is not possible if the table is not empty.
- Added the required column `updatedAt` to the `Reply` table without a default value. This is not possible if the table is not empty.
- Added the required column `updatedAt` to the `Role` table without a default value. This is not possible if the table is not empty.
- Added the required column `updatedAt` to the `User` table without a default value. This is not possible if the table is not empty.
- Added the required column `updatedAt` to the `UserAuth` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "public"."Category" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE "public"."Channel" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "pinnedId" TEXT,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE "public"."Instance" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE "public"."Message" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE "public"."MessagePing" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE "public"."Reply" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE "public"."Role" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE "public"."User" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE "public"."UserAuth" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- CreateTable
CREATE TABLE "public"."ChannelPin" (
"messageId" TEXT NOT NULL,
"channelId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- CreateIndex
CREATE UNIQUE INDEX "ChannelPin_messageId_key" ON "public"."ChannelPin"("messageId");
-- CreateIndex
CREATE UNIQUE INDEX "ChannelPin_channelId_key" ON "public"."ChannelPin"("channelId");
-- CreateIndex
CREATE UNIQUE INDEX "ChannelPin_messageId_channelId_key" ON "public"."ChannelPin"("messageId", "channelId");
-- AddForeignKey
ALTER TABLE "public"."ChannelPin" ADD CONSTRAINT "ChannelPin_messageId_fkey" FOREIGN KEY ("messageId") REFERENCES "public"."Message"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."ChannelPin" ADD CONSTRAINT "ChannelPin_channelId_fkey" FOREIGN KEY ("channelId") REFERENCES "public"."Channel"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,26 @@
-- AlterTable
ALTER TABLE "public"."Category" ALTER COLUMN "updatedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "public"."Channel" ALTER COLUMN "updatedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "public"."Channel" ALTER COLUMN "pinnedId" DROP NOT NULL;
-- AlterTable
ALTER TABLE "public"."Instance" ALTER COLUMN "updatedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "public"."Message" ALTER COLUMN "updatedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "public"."Reply" ALTER COLUMN "updatedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "public"."Role" ALTER COLUMN "updatedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "public"."User" ALTER COLUMN "updatedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "public"."UserAuth" ALTER COLUMN "updatedAt" DROP DEFAULT;

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"

390
concord-server/package-lock.json generated Normal file
View File

@@ -0,0 +1,390 @@
{
"name": "concord-server",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "concord-server",
"dependencies": {
"@prisma/client": "^6.16.2",
"hono": "^4.9.9",
"prisma": "^6.16.2"
},
"devDependencies": {
"@types/bun": "latest"
}
},
"node_modules/@prisma/client": {
"version": "6.16.2",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.16.2.tgz",
"integrity": "sha512-E00PxBcalMfYO/TWnXobBVUai6eW/g5OsifWQsQDzJYm7yaY+IRLo7ZLsaefi0QkTpxfuhFcQ/w180i6kX3iJw==",
"hasInstallScript": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.18"
},
"peerDependencies": {
"prisma": "*",
"typescript": ">=5.1.0"
},
"peerDependenciesMeta": {
"prisma": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/@prisma/config": {
"version": "6.16.2",
"license": "Apache-2.0",
"dependencies": {
"c12": "3.1.0",
"deepmerge-ts": "7.1.5",
"effect": "3.16.12",
"empathic": "2.0.0"
}
},
"node_modules/@prisma/debug": {
"version": "6.16.2",
"license": "Apache-2.0"
},
"node_modules/@prisma/engines": {
"version": "6.16.2",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.16.2",
"@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
"@prisma/fetch-engine": "6.16.2",
"@prisma/get-platform": "6.16.2"
}
},
"node_modules/@prisma/engines-version": {
"version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
"license": "Apache-2.0"
},
"node_modules/@prisma/fetch-engine": {
"version": "6.16.2",
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.16.2",
"@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
"@prisma/get-platform": "6.16.2"
}
},
"node_modules/@prisma/get-platform": {
"version": "6.16.2",
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.16.2"
}
},
"node_modules/@standard-schema/spec": {
"version": "1.0.0",
"license": "MIT"
},
"node_modules/@types/bun": {
"version": "1.2.22",
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.2.22.tgz",
"integrity": "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA==",
"dev": true,
"license": "MIT",
"dependencies": {
"bun-types": "1.2.22"
}
},
"node_modules/@types/node": {
"version": "24.5.2",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.12.0"
}
},
"node_modules/@types/react": {
"version": "19.1.14",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/bun-types": {
"version": "1.2.22",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
},
"peerDependencies": {
"@types/react": "^19"
}
},
"node_modules/c12": {
"version": "3.1.0",
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.3",
"confbox": "^0.2.2",
"defu": "^6.1.4",
"dotenv": "^16.6.1",
"exsolve": "^1.0.7",
"giget": "^2.0.0",
"jiti": "^2.4.2",
"ohash": "^2.0.11",
"pathe": "^2.0.3",
"perfect-debounce": "^1.0.0",
"pkg-types": "^2.2.0",
"rc9": "^2.1.2"
},
"peerDependencies": {
"magicast": "^0.3.5"
},
"peerDependenciesMeta": {
"magicast": {
"optional": true
}
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/citty": {
"version": "0.1.6",
"license": "MIT",
"dependencies": {
"consola": "^3.2.3"
}
},
"node_modules/confbox": {
"version": "0.2.2",
"license": "MIT"
},
"node_modules/consola": {
"version": "3.4.2",
"license": "MIT",
"engines": {
"node": "^14.18.0 || >=16.10.0"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"dev": true,
"license": "MIT"
},
"node_modules/deepmerge-ts": {
"version": "7.1.5",
"license": "BSD-3-Clause",
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/defu": {
"version": "6.1.4",
"license": "MIT"
},
"node_modules/destr": {
"version": "2.0.5",
"license": "MIT"
},
"node_modules/dotenv": {
"version": "16.6.1",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/effect": {
"version": "3.16.12",
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"fast-check": "^3.23.1"
}
},
"node_modules/empathic": {
"version": "2.0.0",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/exsolve": {
"version": "1.0.7",
"license": "MIT"
},
"node_modules/fast-check": {
"version": "3.23.2",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/dubzzz"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fast-check"
}
],
"license": "MIT",
"dependencies": {
"pure-rand": "^6.1.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/giget": {
"version": "2.0.0",
"license": "MIT",
"dependencies": {
"citty": "^0.1.6",
"consola": "^3.4.0",
"defu": "^6.1.4",
"node-fetch-native": "^1.6.6",
"nypm": "^0.6.0",
"pathe": "^2.0.3"
},
"bin": {
"giget": "dist/cli.mjs"
}
},
"node_modules/hono": {
"version": "4.9.9",
"license": "MIT",
"engines": {
"node": ">=16.9.0"
}
},
"node_modules/jiti": {
"version": "2.6.0",
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/node-fetch-native": {
"version": "1.6.7",
"license": "MIT"
},
"node_modules/nypm": {
"version": "0.6.2",
"license": "MIT",
"dependencies": {
"citty": "^0.1.6",
"consola": "^3.4.2",
"pathe": "^2.0.3",
"pkg-types": "^2.3.0",
"tinyexec": "^1.0.1"
},
"bin": {
"nypm": "dist/cli.mjs"
},
"engines": {
"node": "^14.16.0 || >=16.10.0"
}
},
"node_modules/ohash": {
"version": "2.0.11",
"license": "MIT"
},
"node_modules/pathe": {
"version": "2.0.3",
"license": "MIT"
},
"node_modules/perfect-debounce": {
"version": "1.0.0",
"license": "MIT"
},
"node_modules/pkg-types": {
"version": "2.3.0",
"license": "MIT",
"dependencies": {
"confbox": "^0.2.2",
"exsolve": "^1.0.7",
"pathe": "^2.0.3"
}
},
"node_modules/prisma": {
"version": "6.16.2",
"hasInstallScript": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@prisma/config": "6.16.2",
"@prisma/engines": "6.16.2"
},
"bin": {
"prisma": "build/index.js"
},
"engines": {
"node": ">=18.18"
},
"peerDependencies": {
"typescript": ">=5.1.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/pure-rand": {
"version": "6.1.0",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/dubzzz"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fast-check"
}
],
"license": "MIT"
},
"node_modules/rc9": {
"version": "2.1.2",
"license": "MIT",
"dependencies": {
"defu": "^6.1.4",
"destr": "^2.0.3"
}
},
"node_modules/readdirp": {
"version": "4.1.2",
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/tinyexec": {
"version": "1.0.1",
"license": "MIT"
},
"node_modules/undici-types": {
"version": "7.12.0",
"dev": true,
"license": "MIT"
}
}
}

View File

@@ -4,10 +4,19 @@
"dev": "bun run --hot src/index.ts" "dev": "bun run --hot src/index.ts"
}, },
"dependencies": { "dependencies": {
"@hono/standard-validator": "^0.1.5",
"@hono/zod-validator": "^0.7.3",
"@prisma/client": "^6.16.2",
"@scalar/hono-api-reference": "^0.9.19",
"@socket.io/bun-engine": "^0.0.3",
"hono": "^4.9.9", "hono": "^4.9.9",
"prisma": "^6.16.2" "hono-openapi": "^1.1.0",
"prisma": "^6.16.2",
"socket.io": "^4.8.1",
"zod": "^4.1.11"
}, },
"devDependencies": { "devDependencies": {
"@types/bun": "latest" "@types/bun": "latest",
"prisma-zod-generator": "^1.22.1"
} }
} }

View File

@@ -0,0 +1,135 @@
generator client {
provider = "prisma-client-js"
}
generator zod {
provider = "prisma-zod-generator"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Instance {
id String @id @default(uuid(7))
name String
icon String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Role Role[]
Category Category[]
}
model User {
id String @id @default(uuid(7))
username String
nickname String?
bio String?
picture String?
banner String?
admin Boolean
status String // online/offline/dnd/idle/invis
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Role Role[]
UserAuth UserAuth?
Message Message[]
MessagePing MessagePing[]
}
model Role {
User User @relation(fields: [userId], references: [id])
userId String
Instance Instance @relation(fields: [instanceId], references: [id])
instanceId String
type String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, instanceId])
}
model UserAuth {
User User @relation(fields: [userId], references: [id])
userId String
password String // HASHED PASSWORD AS STRING USING SHA-256
token String? // current user token
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId])
}
model Category {
id String @id @default(uuid(7))
Instance Instance? @relation(fields: [instanceId], references: [id])
instanceId String?
name String
position Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Channel Channel[]
}
model Channel {
id String @id @default(uuid(7))
type String
Category Category? @relation(fields: [categoryId], references: [id])
categoryId String?
name String
description String
pinnedId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Message Message[]
ChannelPin ChannelPin?
}
model ChannelPin {
messageId String @unique
channelId String @unique
Message Message @relation(fields: [messageId], references: [id])
Channel Channel @relation(fields: [channelId], references: [id])
createdAt DateTime @default(now())
@@unique([messageId, channelId])
}
model Message {
id String @id @default(uuid(7))
Channel Channel @relation(fields: [channelId], references: [id])
channelId String
User User @relation(fields: [userId], references: [id])
userId String
deleted Boolean
text String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
replies Reply? @relation("MessageToReply")
repliedTo Reply? @relation("ReplyToMessage")
MessagePing MessagePing[]
ChannelPin ChannelPin?
}
model Reply {
message Message @relation("MessageToReply", fields: [messageId], references: [id]) //message text
messageId String @unique //message id of the reply
repliesTo Message @relation("ReplyToMessage", fields: [repliesToId], references: [id]) //message id that this message replies to
repliesToId String @unique //replies to this message id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([messageId, repliesToId])
}
model MessagePing {
Message Message @relation(fields: [messageId], references: [id])
messageId String
PingsUser User @relation(fields: [pingsUserId], references: [id])
pingsUserId String
createdAt DateTime @default(now())
@@unique([messageId, pingsUserId])
}

View File

@@ -0,0 +1,40 @@
import {
createCategory,
getCategory,
getCategoriesByInstance,
updateCategory,
deleteCategory,
deleteAllCategoriesFromInstance,
} from "../services/channelService";
import {
CreateCategoryInput,
UpdateCategoryInput,
DeleteCategoryInput,
DeleteCategoriesByInstanceIdInput,
} from "../validators/categoryValidator";
export async function createNewCategory(data: CreateCategoryInput) {
return await createCategory(data);
}
export async function fetchCategoryData(id: string) {
return await getCategory(id);
}
export async function fetchCategoriesByInstance(instanceId: string) {
return await getCategoriesByInstance(instanceId);
}
export async function updateExistingCategory(data: UpdateCategoryInput) {
return await updateCategory(data);
}
export async function deleteExistingCategory(data: DeleteCategoryInput) {
return await deleteCategory(data);
}
export async function deleteAllCategoriesByInstance(
data: DeleteCategoriesByInstanceIdInput,
) {
return await deleteAllCategoriesFromInstance(data);
}

View File

@@ -0,0 +1,40 @@
import {
createChannel,
getChannel,
getChannelsByCategory,
updateChannel,
deleteChannel,
deleteAllChannelsFromCategory,
} from "../services/channelService";
import {
CreateChannelInput,
UpdateChannelInput,
DeleteChannelInput,
DeleteChannelsByCategoryIdInput,
} from "../validators/channelValidator";
export async function createNewChannel(data: CreateChannelInput) {
return await createChannel(data);
}
export async function fetchChannelData(id: string) {
return await getChannel(id);
}
export async function fetchChannelsByCategory(categoryId: string) {
return await getChannelsByCategory(categoryId);
}
export async function updateExistingChannel(data: UpdateChannelInput) {
return await updateChannel(data);
}
export async function deleteExistingChannel(data: DeleteChannelInput) {
return await deleteChannel(data);
}
export async function deleteAllChannelsByCategory(
data: DeleteChannelsByCategoryIdInput,
) {
return await deleteAllChannelsFromCategory(data);
}

View File

@@ -0,0 +1,10 @@
import { createInstance, getAllInstances } from "../services/instanceService";
import { CreateInstanceRequest } from "../validators/instanceValidator";
export async function createInstanceReq(data: CreateInstanceRequest) {
return await createInstance(data);
}
export async function getAllInstancesReq() {
return await getAllInstances();
}

View File

@@ -0,0 +1,29 @@
import {
getMessageInformation,
getMessagesBefore,
sendMessageToChannel,
} from "../services/messageService";
export async function fetchMessageData(id: string) {
return await getMessageInformation(id);
}
export async function fetchMessagesBefore(date: string, channelId: string) {
return getMessagesBefore(date, channelId);
}
export async function sendMessage(
channelId: string,
userId: string,
content: string,
token: string,
repliedMessageId: string | null,
) {
return await sendMessageToChannel(
channelId,
userId,
content,
token,
repliedMessageId,
);
}

View File

@@ -0,0 +1,126 @@
import { Context } from "hono";
import {
sendMessageToChannel,
removeMessageFromChannel,
} from "../services/realtime.js";
import { success } from "zod";
export async function postMessageToChannel(io: any, c: Context) {
try {
io = c.get("io");
const instanceId = c.req.param("instanceId");
const categoryId = c.req.param("categoryId");
const channelId = c.req.param("channelId");
const message = await c.req.json();
const result = await sendMessageToChannel(
instanceId,
categoryId,
channelId,
message,
"new_channel_message",
io,
);
if (result === "Event not implemented") {
console.log(
"controller::realtime::postMessageToChannel - Failed to send message",
);
return c.json({
success: false,
message: "Event not implemented or recognized",
status: 400,
});
}
if (result === "no acknowledgment") {
console.log(
"controller::realtime::postMessageToChannel - No acknowledgment received from client",
);
return c.json({
success: false,
message: "No acknowledgment received from client",
status: 500,
});
}
if (!result) {
throw new Error("failed to send message");
}
return c.json({
success: true,
message: "Message sent successfully",
status: 200,
});
} catch (err) {
const errMessage = err as Error;
console.log("controller::realtime::postMessageToChannel - ", errMessage);
return c.json({
success: false,
message: errMessage.message,
status: 500,
});
}
}
export async function deleteMessageFromChannel(io: any, c: Context) {
try {
io = c.get("io");
const instanceId = c.req.param("instanceId");
const categoryId = c.req.param("categoryId");
const channelId = c.req.param("channelId");
const messageId = c.req.param("messageId");
const result = await removeMessageFromChannel(
instanceId,
categoryId,
channelId,
messageId,
"delete_channel_message",
io,
);
if (result === "event not implemented") {
console.log(
"controller::realtime::deleteMessageFromChannel - Event not implemented",
);
return c.json({
success: false,
message: "Event not implemented or recognized",
status: 400,
});
}
if (result === "no acknowledgment") {
console.log(
"controller::realtime::deleteMessageFromChannel - No acknowledgment received from client",
);
return c.json({
success: false,
message: "No acknowledgment received from client",
status: 500,
});
}
if (!result) {
throw new Error("failed to delete message");
}
c.json({
success: true,
message: "Message deleted successfully",
status: 200,
});
} catch (err) {
const errMessage = err as Error;
console.log("services::realtime::deleteMessageFromChannel - ", errMessage);
return c.json({
success: false,
message: errMessage.message,
status: 500,
});
}
}

View File

@@ -0,0 +1,18 @@
import {
getAllUsersFrom,
getUserInformation,
createUser,
} from "../services/userService";
import { CreateUserInput } from "../validators/userValidator";
export async function fetchUserData(id: string) {
return await getUserInformation(id);
}
export async function fetchAllUsers(instanceId: string) {
return await getAllUsersFrom(instanceId);
}
export async function createNewUser(data: CreateUserInput) {
return await createUser(data);
}

View File

@@ -0,0 +1,5 @@
import * as crypto from "crypto";
export default function shaHash(data: string, salt: string): string {
return crypto.createHmac("sha256", salt).update(data).digest("hex");
}

View File

@@ -1,9 +1,71 @@
import { Hono } from 'hono' import { Hono } from "hono";
import { cors } from "hono/cors";
import { Server as Engine } from "@socket.io/bun-engine";
import { Server } from "socket.io";
import routes from "./routes/index";
import { Scalar } from "@scalar/hono-api-reference";
import { openAPIRouteHandler } from "hono-openapi";
const app = new Hono() //initialize socket.io server
const io = new Server();
app.get('/', (c) => { //initialize bun engine
return c.text('Hello Hono!') //then bind to socket.io server
}) const engine = new Engine();
io.bind(engine);
export default app io.on("connection", (socket) => {
//get userId and clientId from query params
const userId = socket.handshake.query.userId;
const clientId = socket.handshake.query.clientId;
if (!userId || Array.isArray(userId)) {
socket.disconnect();
throw new Error("Invalid user ID");
}
if (!clientId || Array.isArray(clientId)) {
socket.disconnect();
throw new Error("Invalid client ID");
}
socket.join(userId);
console.log(
`User ${userId} connected. Client ID ${clientId} on socket ${socket.id}`,
);
socket.on("disconnect", () => {
console.log(`User ${userId} disconnected from socket ${socket.id}`);
});
});
const app = new Hono();
app.use(
"*",
cors({
origin: "http://localhost:5173",
allowHeaders: ["Content-Type", "Authorization"],
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
credentials: true,
}),
);
app.route("/api", routes);
app.get(
"/openapi",
openAPIRouteHandler(app, {
documentation: {
info: {
title: "Hono API",
version: "1.0.0",
description: "Greeting API",
},
servers: [{ url: "http://localhost:3000", description: "Local Server" }],
},
}),
);
app.get("/scalar", Scalar({ url: "/openapi" }));
export default app;

View File

@@ -0,0 +1,315 @@
import {
createNewCategory,
fetchCategoryData,
fetchCategoriesByInstance,
updateExistingCategory,
deleteExistingCategory,
deleteAllCategoriesByInstance,
} from "../controller/categoryController";
import {
createCategorySchema,
getCategorySchema,
getCategoriesByInstanceIdSchema,
updateCategorySchema,
deleteCategorySchema,
deleteCategoriesByInstanceIdSchema,
CreateCategoryInput,
GetCategoryInput,
GetCategoriesByInstanceIdInput,
UpdateCategoryInput,
DeleteCategoryInput,
DeleteCategoriesByInstanceIdInput,
} from "../validators/categoryValidator";
import { zValidator } from "@hono/zod-validator";
import { Hono } from "hono";
import { describeRoute, resolver } from "hono-openapi";
const categoryRoutes = new Hono();
// Create a new category
categoryRoutes.post(
"/",
describeRoute({
description: "Create a new category",
responses: {
200: {
description: "Success creating category",
content: {
"application/json": { schema: resolver(createCategorySchema) },
},
},
400: {
description: "Bad Request - Invalid input data",
content: {
"application/json": { schema: resolver(createCategorySchema) },
},
},
401: {
description: "Unauthorized - Admin access required",
content: {
"application/json": { schema: resolver(createCategorySchema) },
},
},
404: {
description: "User Id not found",
content: {
"application/json": { schema: resolver(createCategorySchema) },
},
},
},
}),
zValidator("json", createCategorySchema),
async (c) => {
const data = c.req.valid("json") as CreateCategoryInput;
const categoryData = await createNewCategory(data);
if (categoryData) {
return c.json(categoryData);
} else {
return c.json({ error: "Failed to create category" }, 400);
}
},
);
// Get a category by ID
categoryRoutes.get(
"/:id",
describeRoute({
description: "Get category by id",
responses: {
200: {
description: "Success getting category",
content: {
"application/json": { schema: resolver(getCategorySchema) },
},
},
404: {
description: "Category id not found",
content: {
"application/json": { schema: resolver(getCategorySchema) },
},
},
},
}),
async (c) => {
const id = c.req.param("id");
const categoryData = await fetchCategoryData(id);
if (categoryData) {
return c.json(categoryData);
} else {
return c.json({ error: "Category not found" }, 404);
}
},
);
// Get all categories by instance ID
categoryRoutes.get(
"/instance/:instanceId",
describeRoute({
description: "Get all categories by instance id",
responses: {
200: {
description: "Success getting all categories in instance",
content: {
"application/json": {
schema: resolver(getCategoriesByInstanceIdSchema),
},
},
},
400: {
description: "Bad Request - Missing instance ID",
content: {
"application/json": {
schema: resolver(getCategoriesByInstanceIdSchema),
},
},
},
},
}),
async (c) => {
const instanceId = c.req.param("instanceId");
if (!instanceId) {
return c.json({ error: "No instance id provided" }, 400);
}
const categoryData = await fetchCategoriesByInstance(instanceId);
if (categoryData) {
return c.json(categoryData);
} else {
return c.json(
{ error: "Error getting all categories from instance" },
500,
);
}
},
);
// Update a category
categoryRoutes.put(
"/:id",
describeRoute({
description: "Update an existing category",
responses: {
200: {
description: "Success updating category",
content: {
"application/json": { schema: resolver(updateCategorySchema) },
},
},
400: {
description: "Bad Request - Invalid input data",
content: {
"application/json": { schema: resolver(updateCategorySchema) },
},
},
401: {
description: "Unauthorized - Admin access required",
content: {
"application/json": { schema: resolver(updateCategorySchema) },
},
},
404: {
description: "Category id or User Id not found",
content: {
"application/json": { schema: resolver(updateCategorySchema) },
},
},
},
}),
zValidator("json", updateCategorySchema),
async (c) => {
const id = c.req.param("id");
const data = c.req.valid("json") as UpdateCategoryInput;
// Ensure the ID in the path matches the one in the body
if (data.id && data.id !== id) {
return c.json({ error: "ID in path does not match ID in body" }, 400);
}
// Set ID from path if not in body
if (!data.id) {
data.id = id;
}
const categoryData = await updateExistingCategory(data);
if (categoryData) {
return c.json(categoryData);
} else {
return c.json({ error: "Failed to update category" }, 400);
}
},
);
// Delete a specific category
categoryRoutes.delete(
"/:id",
describeRoute({
description: "Delete an existing category",
responses: {
200: {
description: "Success deleting category",
content: {
"application/json": { schema: resolver(deleteCategorySchema) },
},
},
400: {
description: "Bad Request - Invalid input data",
content: {
"application/json": { schema: resolver(deleteCategorySchema) },
},
},
401: {
description: "Unauthorized - Admin access required",
content: {
"application/json": { schema: resolver(deleteCategorySchema) },
},
},
404: {
description: "Category id or User Id not found",
content: {
"application/json": { schema: resolver(deleteCategorySchema) },
},
},
},
}),
zValidator("json", deleteCategorySchema),
async (c) => {
const id = c.req.param("id");
const data = c.req.valid("json") as DeleteCategoryInput;
// Ensure the ID in the path matches the one in the body
if (data.id !== id) {
return c.json({ error: "ID in path does not match ID in body" }, 400);
}
const categoryData = await deleteExistingCategory(data);
if (categoryData) {
return c.json(categoryData);
} else {
return c.json({ error: "Failed to delete category" }, 400);
}
},
);
// Delete all categories by instance ID
categoryRoutes.delete(
"/instance/:instanceId",
describeRoute({
description: "Delete all categories by instance id",
responses: {
200: {
description: "Success deleting all categories in instance",
content: {
"application/json": {
schema: resolver(deleteCategoriesByInstanceIdSchema),
},
},
},
400: {
description: "Bad Request - Invalid input data",
content: {
"application/json": {
schema: resolver(deleteCategoriesByInstanceIdSchema),
},
},
},
401: {
description: "Unauthorized - Admin access required",
content: {
"application/json": {
schema: resolver(deleteCategoriesByInstanceIdSchema),
},
},
},
404: {
description: "Instance id or User Id not found",
content: {
"application/json": {
schema: resolver(deleteCategoriesByInstanceIdSchema),
},
},
},
},
}),
zValidator("json", deleteCategoriesByInstanceIdSchema),
async (c) => {
const instanceId = c.req.param("instanceId");
const data = c.req.valid("json") as DeleteCategoriesByInstanceIdInput;
// Ensure the instanceId in the path matches the one in the body
if (data.instanceId !== instanceId) {
return c.json(
{ error: "Instance ID in path does not match Instance ID in body" },
400,
);
}
const categoryData = await deleteAllCategoriesByInstance(data);
if (categoryData) {
return c.json(categoryData);
} else {
return c.json({ error: "Failed to delete categories" }, 400);
}
},
);
export { categoryRoutes };

View File

@@ -0,0 +1,313 @@
import {
createNewChannel,
fetchChannelData,
fetchChannelsByCategory,
updateExistingChannel,
deleteExistingChannel,
deleteAllChannelsByCategory,
} from "../controller/channelController";
import {
createChannelSchema,
getChannelSchema,
getChannelsByCategoryIdSchema,
updateChannelSchema,
deleteChannelSchema,
deleteChannelsByCategoryIdSchema,
CreateChannelInput,
GetChannelInput,
GetChannelsByCategoryIdInput,
UpdateChannelInput,
DeleteChannelInput,
DeleteChannelsByCategoryIdInput,
} from "../validators/channelValidator";
import { zValidator } from "@hono/zod-validator";
import { Hono } from "hono";
import { describeRoute, resolver } from "hono-openapi";
const channelRoutes = new Hono();
// Create a new channel
channelRoutes.post(
"/",
describeRoute({
description: "Create a new channel",
responses: {
200: {
description: "Success creating channel",
content: {
"application/json": { schema: resolver(createChannelSchema) },
},
},
400: {
description: "Bad Request - Invalid input data",
content: {
"application/json": { schema: resolver(createChannelSchema) },
},
},
401: {
description: "Unauthorized - Admin access required",
content: {
"application/json": { schema: resolver(createChannelSchema) },
},
},
404: {
description: "User Id not found",
content: {
"application/json": { schema: resolver(createChannelSchema) },
},
},
},
}),
zValidator("json", createChannelSchema),
async (c) => {
const data = c.req.valid("json") as CreateChannelInput;
const channelData = await createNewChannel(data);
if (channelData) {
return c.json(channelData);
} else {
return c.json({ error: "Failed to create channel" }, 400);
}
},
);
// Get a channel by ID
channelRoutes.get(
"/:id",
describeRoute({
description: "Get channel by id",
responses: {
200: {
description: "Success getting channel",
content: {
"application/json": { schema: resolver(getChannelSchema) },
},
},
404: {
description: "Channel id not found",
content: {
"application/json": { schema: resolver(getChannelSchema) },
},
},
},
}),
async (c) => {
const id = c.req.param("id");
const channelData = await fetchChannelData(id);
if (channelData) {
return c.json(channelData);
} else {
return c.json({ error: "Channel not found" }, 404);
}
},
);
// Get all channels by category ID
channelRoutes.get(
"/category/:categoryId",
describeRoute({
description: "Get all channels by category id",
responses: {
200: {
description: "Success getting all channels in category",
content: {
"application/json": {
schema: resolver(getChannelsByCategoryIdSchema),
},
},
},
400: {
description: "Bad Request - Missing category ID",
content: {
"application/json": {
schema: resolver(getChannelsByCategoryIdSchema),
},
},
},
},
}),
async (c) => {
const categoryId = c.req.param("categoryId");
if (!categoryId) {
return c.json({ error: "No category id provided" }, 400);
}
const channels = await fetchChannelsByCategory(categoryId);
if (channels) {
return c.json(channels);
} else {
return c.json({ error: "Error getting channels from category" }, 500);
}
},
);
// Update a channel
channelRoutes.put(
"/:id",
describeRoute({
description: "Update an existing channel",
responses: {
200: {
description: "Success updating channel",
content: {
"application/json": { schema: resolver(updateChannelSchema) },
},
},
400: {
description: "Bad Request - Invalid input data",
content: {
"application/json": { schema: resolver(updateChannelSchema) },
},
},
401: {
description: "Unauthorized - Admin access required",
content: {
"application/json": { schema: resolver(updateChannelSchema) },
},
},
404: {
description: "Channel id or User Id not found",
content: {
"application/json": { schema: resolver(updateChannelSchema) },
},
},
},
}),
zValidator("json", updateChannelSchema),
async (c) => {
const id = c.req.param("id");
const data = c.req.valid("json") as UpdateChannelInput;
// Ensure the ID in the path matches the one in the body
if (data.id && data.id !== id) {
return c.json({ error: "ID in path does not match ID in body" }, 400);
}
// Set ID from path if not in body
if (!data.id) {
data.id = id;
}
const result = await updateExistingChannel(data);
if (result) {
return c.json(result);
} else {
return c.json({ error: "Failed to update channel" }, 400);
}
},
);
// Delete a specific channel
channelRoutes.delete(
"/:id",
describeRoute({
description: "Delete an existing channel",
responses: {
200: {
description: "Success deleting channel",
content: {
"application/json": { schema: resolver(deleteChannelSchema) },
},
},
400: {
description: "Bad Request - Invalid input data",
content: {
"application/json": { schema: resolver(deleteChannelSchema) },
},
},
401: {
description: "Unauthorized - Admin access required",
content: {
"application/json": { schema: resolver(deleteChannelSchema) },
},
},
404: {
description: "Channel id or User Id not found",
content: {
"application/json": { schema: resolver(deleteChannelSchema) },
},
},
},
}),
zValidator("json", deleteChannelSchema),
async (c) => {
const id = c.req.param("id");
const data = c.req.valid("json") as DeleteChannelInput;
// Ensure the ID in the path matches the one in the body
if (data.id !== id) {
return c.json({ error: "ID in path does not match ID in body" }, 400);
}
const result = await deleteExistingChannel(data);
if (result) {
return c.json({ success: true });
} else {
return c.json({ error: "Failed to delete channel" }, 400);
}
},
);
// Delete all channels by category ID
channelRoutes.delete(
"/category/:categoryId",
describeRoute({
description: "Delete all channels by category id",
responses: {
200: {
description: "Success deleting all channels in category",
content: {
"application/json": {
schema: resolver(deleteChannelsByCategoryIdSchema),
},
},
},
400: {
description: "Bad Request - Invalid input data",
content: {
"application/json": {
schema: resolver(deleteChannelsByCategoryIdSchema),
},
},
},
401: {
description: "Unauthorized - Admin access required",
content: {
"application/json": {
schema: resolver(deleteChannelsByCategoryIdSchema),
},
},
},
404: {
description: "Category id or User Id not found",
content: {
"application/json": {
schema: resolver(deleteChannelsByCategoryIdSchema),
},
},
},
},
}),
zValidator("json", deleteChannelsByCategoryIdSchema),
async (c) => {
const categoryId = c.req.param("categoryId");
const data = c.req.valid("json") as DeleteChannelsByCategoryIdInput;
// Ensure the categoryId in the path matches the one in the body
if (data.categoryId !== categoryId) {
return c.json(
{ error: "Category ID in path does not match Category ID in body" },
400,
);
}
const result = await deleteAllChannelsByCategory(data);
if (result) {
return c.json({ success: true });
} else {
return c.json({ error: "Failed to delete channels" }, 400);
}
},
);
export { channelRoutes };

View File

@@ -0,0 +1,17 @@
//place exported routes below this line
import { Hono } from "hono";
import userRoutes from "./userRoutes";
import messageRoutes from "./messageRoutes";
import { channelRoutes } from "./channelRoutes";
import instanceRoutes from "./instanceRoutes";
import { categoryRoutes } from "./categoryRoutes";
const routes = new Hono();
routes.route("/user", userRoutes);
routes.route("/message", messageRoutes);
routes.route("/channel", channelRoutes);
routes.route("/instance", instanceRoutes);
routes.route("/category", categoryRoutes);
export default routes;

View File

@@ -0,0 +1,77 @@
import { Hono } from "hono";
import { describeRoute, resolver } from "hono-openapi";
import {
createInstanceRequestSchema,
getAllInstancesResponseSchema,
} from "../validators/instanceValidator";
import { zValidator } from "@hono/zod-validator";
import {
createInstanceReq,
getAllInstancesReq,
} from "../controller/instanceController";
const instanceRoutes = new Hono();
instanceRoutes.post(
"",
describeRoute({
description: "Create instance",
responses: {
200: {
description: "Instance created",
content: {
"application/json": { schema: resolver(createInstanceRequestSchema) },
},
},
400: {
description: "Invalid request",
},
},
}),
zValidator("json", createInstanceRequestSchema),
async (c) => {
const data = await c.req.json();
if (!data) {
return c.json({ error: "could not parse data" }, 400);
}
const instance = await createInstanceReq(data);
return c.json(instance, 201);
},
);
instanceRoutes.get(
"",
describeRoute({
description: "Get all instances",
responses: {
200: {
description: "List of all instances",
content: {
"application/json": {
schema: resolver(getAllInstancesResponseSchema),
},
},
},
500: {
description: "Server error",
},
},
}),
async (c) => {
const instances = await getAllInstancesReq();
if (instances.success) {
return c.json(instances, 200);
} else {
return c.json(
{
success: false,
error: instances.error || "Failed to fetch instances",
},
500,
);
}
},
);
export default instanceRoutes;

View File

@@ -0,0 +1,146 @@
import { Hono } from "hono";
import { describeResponse, describeRoute, resolver } from "hono-openapi";
import {
getMessageByIdSchema,
getMessagesBeforeDate,
sendMessageSchema,
} from "../validators/messageValidator";
import { zValidator } from "@hono/zod-validator";
import {
fetchMessageData,
fetchMessagesBefore,
sendMessage,
} from "../controller/messageController";
const messageRoutes = new Hono();
messageRoutes.get(
"/:id",
describeRoute({
description: "Get message by id",
responses: {
200: {
description: "Success getting message",
content: {
"application/json": { schema: resolver(getMessageByIdSchema) },
},
},
404: {
description: "Message id not found",
content: {
"application/json": { schema: resolver(getMessageByIdSchema) },
},
},
},
}),
zValidator("param", getMessageByIdSchema),
async (c) => {
const id = c.req.param("id");
const messageData = await fetchMessageData(id);
if (messageData) {
return c.json(messageData, 200);
} else {
return c.json({ error: "Message not found" }, 404);
}
},
);
messageRoutes.get(
"",
describeRoute({
description: "Get up to 50 messages prior to given datetime",
responses: {
200: {
description: "Success getting up to 50 messages",
content: {
"application/json": { schema: resolver(getMessagesBeforeDate) },
},
},
},
}),
zValidator("query", getMessagesBeforeDate),
async (c) => {
const date = c.req.query("date");
if (!date) {
return c.json({ error: "date not provided" }, 400);
}
const channelId = c.req.query("channelId");
if (!channelId) {
return c.json({ error: "channelId not provided" }, 400);
}
const messagesArr = await fetchMessagesBefore(date, channelId);
if (messagesArr) {
return c.json(messagesArr, 200);
} else {
return c.json({ error: "Failed to fetch messages" }, 500);
}
},
);
messageRoutes.post(
"",
describeRoute({
description: "Send a message to a channel",
responses: {
201: {
description: "Message sent successfully",
content: {
"application/json": { schema: resolver(sendMessageSchema) },
},
},
401: {
description: "Unauthorized - invalid token or user credentials",
content: {
"application/json": {
schema: {
type: "object",
properties: { error: { type: "string" } },
},
},
},
},
500: {
description: "Server error",
content: {
"application/json": {
schema: {
type: "object",
properties: { error: { type: "string" } },
},
},
},
},
},
}),
zValidator("json", sendMessageSchema),
async (c) => {
const { channelId, userId, content, token, repliedMessageId } =
await c.req.json();
const result = await sendMessage(
channelId,
userId,
content,
token,
repliedMessageId || null,
);
if (result) {
return c.json(result, 201);
} else {
return c.json(
{
error:
"Failed to send message. Check your credentials and try again.",
},
401,
);
}
},
);
export default messageRoutes;

View File

@@ -0,0 +1,29 @@
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { describeRoute, resolver } from "hono-openapi";
import {
postMessageToChannel,
deleteMessageFromChannel,
} from "../controller/realtime";
const app = new Hono();
app.post(
"message/",
zValidator({
body: z.object({
content: z.string().min(1).max(500),
}),
}),
async (c) => {
const { instanceId, categoryId, channelId } = c.req.params;
const { content } = c.req.body;
return postMessageToChannel(c.get("io"), {
instanceId,
categoryId,
channelId,
content,
});
},
);

View File

@@ -0,0 +1,110 @@
import { Hono } from "hono";
import {
fetchAllUsers,
fetchUserData,
createNewUser,
} from "../controller/userController";
import {
createUserSchema,
queryAllUsersByInstanceId,
queryUserByIdSchema,
} from "../validators/userValidator";
import { zValidator } from "@hono/zod-validator";
import { describeRoute, resolver } from "hono-openapi";
const userRoutes = new Hono();
userRoutes.get(
"/:id",
describeRoute({
description: "Get user by id",
responses: {
200: {
description: "Success getting user",
content: {
"application/json": { schema: resolver(queryUserByIdSchema) },
},
},
404: {
description: "User id not found",
content: {
"application/json": { schema: resolver(queryUserByIdSchema) },
},
},
},
}),
zValidator("param", queryUserByIdSchema),
async (c) => {
const id = c.req.param("id");
const userData = await fetchUserData(id);
if (userData) {
return c.json(userData);
} else {
return c.json({ error: "User not found" }, 404);
}
},
);
userRoutes.get(
"",
describeRoute({
description: "Get all users by instance id",
responses: {
200: {
description: "Success getting all users in instance",
content: {
"application/json": { schema: resolver(queryAllUsersByInstanceId) },
},
},
},
}),
zValidator("query", queryAllUsersByInstanceId),
async (c) => {
const instanceId = c.req.query("instanceId");
if (!instanceId) {
return c.json({ error: "No instance id provided" }, 400);
}
const userData = await fetchAllUsers(instanceId);
if (userData) {
return c.json(userData);
} else {
return c.json({ error: "Error getting all users from instance" }, 500);
}
},
);
userRoutes.post(
"",
describeRoute({
description: "Create a new user",
responses: {
201: {
description: "Success",
content: {
"application/json": { schema: resolver(createUserSchema) },
},
},
400: {
description: "Bad request (user exists)",
content: {
"application/json": { schema: resolver(createUserSchema) },
},
},
},
}),
zValidator("json", createUserSchema),
async (c) => {
try {
const data = await c.req.json();
const newUser = await createNewUser(data);
if (!newUser) {
return c.json({ error: "User already exists" }, 400);
}
return c.json(newUser, 201);
} catch (error) {
return c.json({ error: "Error creating user" }, 500);
}
},
);
export default userRoutes;

View File

@@ -0,0 +1,457 @@
import { Channel, Category } from "@prisma/client";
import { PrismaClient } from "@prisma/client";
import { getUserInformation, getUserCredentials } from "./userService";
import {
CreateChannelInput,
UpdateChannelInput,
DeleteChannelInput,
DeleteChannelsByCategoryIdInput,
} from "../validators/channelValidator";
import {
UpdateCategoryInput,
DeleteCategoryInput,
DeleteCategoriesByInstanceIdInput,
CreateCategoryInput,
} from "../validators/categoryValidator";
const prisma = new PrismaClient();
export async function createCategory(
data: CreateCategoryInput,
): Promise<Category | null> {
try {
//Confirm if user exists and is admin
const requestingUser = await getUserInformation(data.requestingUserId);
const requestingUserCredentials = await getUserCredentials(
data.requestingUserId,
);
if (
!requestingUser ||
!requestingUserCredentials ||
!requestingUser.admin ||
requestingUserCredentials.token == null ||
data.requestingUserToken != requestingUserCredentials.token
) {
return null;
}
const newCategory = await prisma.category.create({
data: {
name: data.name,
position: data.position,
},
});
if (!newCategory) {
throw new Error("could not create category");
}
let curInstance;
if (data.instanceId) {
curInstance = await prisma.instance.findUnique({
where: {
id: data.instanceId,
},
include: {
Category: true,
},
});
if (!curInstance) {
throw new Error("could not find instance to add category to");
}
await prisma.category.update({
where: {
id: newCategory.id,
},
data: {
instanceId: curInstance.id,
},
});
return newCategory;
}
return newCategory;
} catch (err) {
console.log("services::channelService::createCategory - ", err);
return null;
}
}
export async function getCategory(
categoryId: string,
): Promise<Category | null> {
try {
const category = await prisma.category.findUnique({
where: {
id: categoryId,
},
});
if (!category) {
throw new Error("could not find category");
}
return category;
} catch (err) {
console.log("services::channelService::getCategory - ", err);
return null;
}
}
export async function getCategoriesByInstance(
instanceId: string,
): Promise<Category[] | null> {
try {
const categories = await prisma.category.findMany({
where: {
instanceId: instanceId,
},
include: {
Channel: true,
},
orderBy: {
position: "asc",
},
});
if (!categories) {
throw new Error("could not find categories for instance");
}
return categories;
} catch (err) {
console.log("services::channelService::getCategoriesByInstance - ", err);
return null;
}
}
export async function updateCategory(
data: UpdateCategoryInput,
): Promise<Category | null> {
try {
//Confirm if user exists and is admin
const requestingUser = await getUserInformation(data.requestingUserId);
const requestingUserCredentials = await getUserCredentials(
data.requestingUserId,
);
if (
!requestingUser ||
!requestingUserCredentials ||
!requestingUser.admin ||
requestingUserCredentials.token == null ||
data.requestingUserToken != requestingUserCredentials.token
) {
return null;
}
const updatedCategory = await prisma.category.update({
where: {
id: data.id,
},
data: {
name: data.name,
position: data.position,
Channel: data.channels ? { set: data.channels } : undefined,
},
});
if (!updatedCategory) {
throw new Error("could not update category");
}
return updatedCategory;
} catch (err) {
console.log("services::channelService::updateCategory - ", err);
return null;
}
}
export async function deleteCategory(
data: DeleteCategoryInput,
): Promise<boolean | null> {
try {
//Confirm if user exists and is admin
const requestingUser = await getUserInformation(data.requestingUserId);
const requestingUserCredentials = await getUserCredentials(
data.requestingUserId,
);
if (
!requestingUser ||
!requestingUserCredentials ||
!requestingUser.admin ||
requestingUserCredentials.token == null ||
data.requestingUserToken != requestingUserCredentials.token
) {
return null;
}
const deleteAllChannels = await prisma.channel.deleteMany({
where: {
categoryId: data.id,
},
});
if (deleteAllChannels.count === 0) {
throw new Error("could not delete channels from category");
}
const deletedCategory = await prisma.category.delete({
where: {
id: data.id,
},
});
if (!deletedCategory) {
throw new Error("could not delete category");
}
return true;
} catch (err) {
console.log("services::channelService::deleteCategory - ", err);
return false;
}
}
export async function deleteAllCategoriesFromInstance(
data: DeleteCategoriesByInstanceIdInput,
): Promise<boolean | null> {
try {
//Confirm if user exists and is admin
const requestingUser = await getUserInformation(data.requestingUserId);
const requestingUserCredentials = await getUserCredentials(
data.requestingUserId,
);
if (
!requestingUser ||
!requestingUserCredentials ||
!requestingUser.admin ||
requestingUserCredentials.token == null ||
data.requestingUserToken != requestingUserCredentials.token
) {
return null;
}
const deletedCategories = await prisma.category.deleteMany({
where: {
instanceId: data.instanceId,
},
});
if (deletedCategories.count === 0) {
throw new Error("could not delete categories from instance");
}
return true;
} catch (err) {
console.log(
"services::channelService::deleteAllCategoriesFromInstance - ",
err,
);
return false;
}
}
export async function createChannel(
data: CreateChannelInput,
): Promise<Channel | null> {
try {
//Confirm if user exists and is admin
const requestingUser = await getUserInformation(data.requestingUserId);
const requestingUserCredentials = await getUserCredentials(
data.requestingUserId,
);
if (
!requestingUser ||
!requestingUserCredentials ||
!requestingUser.admin ||
requestingUserCredentials.token == null ||
data.requestingUserToken != requestingUserCredentials.token
) {
return null;
}
const newChannel = await prisma.channel.create({
data: {
type: data.type,
name: data.name,
description: data.description,
categoryId: data.categoryId ? data.categoryId : null,
},
});
if (!newChannel) {
throw new Error("could not create channel");
}
return newChannel;
} catch (err) {
console.log("services::channelService::createChannel - ", err);
return null;
}
}
export async function getChannel(channelId: string): Promise<Channel | null> {
try {
const channel = await prisma.channel.findUnique({
where: {
id: channelId,
},
});
if (!channel) {
throw new Error("could not find channel");
}
return channel;
} catch (err) {
console.log("services::channelService::getChannel - ", err);
return null;
}
}
export async function getChannelsByCategory(
categoryId: string,
): Promise<Channel[] | null> {
try {
const channels = await prisma.channel.findMany({
where: {
categoryId: categoryId,
},
});
if (!channels) {
throw new Error("could not find channels for category");
}
return channels;
} catch (err) {
console.log("services::channelService::getChannelsByCategory - ", err);
return null;
}
}
export async function updateChannel(
data: UpdateChannelInput,
): Promise<Channel | null> {
try {
//Confirm if user exists and is admin
const requestingUser = await getUserInformation(data.requestingUserId);
const requestingUserCredentials = await getUserCredentials(
data.requestingUserId,
);
if (
!requestingUser ||
!requestingUserCredentials ||
!requestingUser.admin ||
requestingUserCredentials.token == null ||
data.requestingUserToken != requestingUserCredentials.token
) {
return null;
}
const updatedChannel = await prisma.channel.update({
where: {
id: data.id,
},
data: {
name: data.name,
description: data.description,
categoryId: data.categoryId ? data.categoryId : undefined,
},
});
if (!updatedChannel) {
throw new Error("could not update channel");
}
return updatedChannel;
} catch (err) {
console.log("services::channelService::updateChannel - ", err);
return null;
}
}
export async function deleteChannel(
data: DeleteChannelInput,
): Promise<boolean | null> {
try {
//Confirm if user exists and is admin
const requestingUser = await getUserInformation(data.requestingUserId);
const requestingUserCredentials = await getUserCredentials(
data.requestingUserId,
);
if (
!requestingUser ||
!requestingUserCredentials ||
!requestingUser.admin ||
requestingUserCredentials.token == null ||
data.requestingUserToken != requestingUserCredentials.token
) {
return null;
}
const deletedChannel = await prisma.channel.delete({
where: {
id: data.id,
},
});
if (!deletedChannel) {
throw new Error("could not delete channel");
}
return true;
} catch (err) {
console.log("services::channelService::deleteChannel - ", err);
return false;
}
}
export async function deleteAllChannelsFromCategory(
data: DeleteChannelsByCategoryIdInput,
): Promise<boolean | null> {
try {
//Confirm if user exists and is admin
const requestingUser = await getUserInformation(data.requestingUserId);
const requestingUserCredentials = await getUserCredentials(
data.requestingUserId,
);
if (
!requestingUser ||
!requestingUserCredentials ||
!requestingUser.admin ||
requestingUserCredentials.token == null ||
data.requestingUserToken != requestingUserCredentials.token
) {
return null;
}
const deletedChannels = await prisma.channel.deleteMany({
where: {
categoryId: data.categoryId,
},
});
if (deletedChannels.count === 0) {
throw new Error("could not delete channels from category");
}
return true;
} catch (err) {
console.log(
"services::channelService::deleteAllChannelsFromCategory - ",
err,
);
return false;
}
}

View File

@@ -0,0 +1,59 @@
import { PrismaClient } from "@prisma/client";
import { CreateInstanceRequest } from "../validators/instanceValidator";
import { getUserCredentials, getUserInformation } from "./userService";
const prisma = new PrismaClient();
export async function createInstance(data: CreateInstanceRequest) {
try {
const creds = await getUserCredentials(data.requestingUserId);
const user = await getUserInformation(data.requestingUserId);
if (
!creds ||
creds.token != data.requestingUserToken ||
!user ||
!user.admin
) {
return null;
}
const newInstance = await prisma.instance.create({
data: {
name: data.name,
icon: data.icon,
},
});
return {
success: true,
data: newInstance,
};
} catch (error) {
console.error("Error creating instance:", error);
return {
success: false,
error: "Failed to create instance",
};
}
}
export async function getAllInstances() {
try {
const instances = await prisma.instance.findMany({
orderBy: {
createdAt: "desc",
},
});
return {
success: true,
data: instances,
};
} catch (error) {
console.error("Error fetching instances:", error);
return {
success: false,
error: "Failed to fetch instances",
};
}
}

View File

@@ -0,0 +1,230 @@
import { PrismaClient } from "@prisma/client";
import { getUserCredentials } from "./userService";
const prisma = new PrismaClient();
export async function getMessageInformation(id: string): Promise<{
id: string;
channelId: string;
userId: string;
text: string;
deleted: boolean;
replies: null | {
messageId: string;
repliesToId: string;
repliesToText: string;
};
} | null> {
try {
if (!id) {
throw new Error("missing messageId");
}
const message = await prisma.message.findUnique({
where: {
id: id,
},
});
if (!message) {
throw new Error("could not find message");
}
// Check if this message is a reply to another message
const replyData = await prisma.reply.findFirst({
where: {
messageId: id,
},
});
let originalMessage = null;
if (replyData) {
originalMessage = await prisma.message.findUnique({
where: {
id: replyData.repliesToId,
},
});
}
return {
id: message.id,
channelId: message.channelId!,
userId: message.userId!,
text: message.text,
deleted: message.deleted,
replies: originalMessage
? {
messageId: message.id,
repliesToId: originalMessage.id,
repliesToText: originalMessage.text,
}
: null,
};
} catch (err) {
const errMessage = err as Error;
if (errMessage.message === "missing messageId") {
console.log(
"services::actions::getMessageInformation - missing messageId",
);
return null;
}
if (errMessage.message === "could not find message") {
console.log(
"services::actions::getMessageInformation - unable to find message",
);
return null;
}
console.log(
"services::actions::getMessageInformation - unknown error",
errMessage,
);
return null;
}
}
export async function getMessagesBefore(date: string, channelId: string) {
try {
if (!date || !channelId) {
throw new Error("missing date or channelId");
}
const messages = await prisma.message.findMany({
where: {
channelId: channelId,
createdAt: {
lt: new Date(date),
},
},
orderBy: {
createdAt: "desc",
},
take: 50,
});
const messageInformationPromises = messages.map(async (message) => {
const replyData = await prisma.reply.findFirst({
where: {
messageId: message.id,
},
});
let originalMessage = null;
if (replyData) {
originalMessage = await prisma.message.findUnique({
where: {
id: replyData.repliesToId,
},
});
}
return {
id: message.id,
channelId: message.channelId!,
userId: message.userId!,
text: message.text,
deleted: message.deleted,
replies: originalMessage
? {
messageId: message.id,
repliesToId: originalMessage.id,
repliesToText: originalMessage.text,
}
: null,
};
});
return Promise.all(messageInformationPromises);
} catch (err) {
const errMessage = err as Error;
if (errMessage.message === "missing date or channelId") {
console.log(
"services::actions::getMessagesBefore - missing date or channelId",
);
return null;
}
console.log(
"services::actions::getMessagesBefore - unknown error",
errMessage,
);
return null;
}
}
export async function sendMessageToChannel(
channelId: string,
userId: string,
content: string,
token: string,
repliedMessageId: string | null,
): Promise<{
id: string;
channelId: string;
userId: string;
text: string;
deleted: boolean;
replies: null | {
messageId: string;
repliesToId: string;
repliesToText: string;
};
} | null> {
try {
const userCreds = await getUserCredentials(userId);
if (!userCreds || userCreds.token != token) {
return null;
}
const newMessage = await prisma.message.create({
data: {
channelId: channelId,
userId: userId,
text: content,
deleted: false,
},
});
if (!newMessage) {
return null;
}
let origMessage;
if (repliedMessageId) {
origMessage = await prisma.message.findUnique({
where: {
id: repliedMessageId,
},
});
if (!origMessage) {
throw new Error("could not find original message to reply to");
}
await prisma.reply.create({
data: {
messageId: newMessage.id,
repliesToId: origMessage.id,
},
});
}
return {
...newMessage,
channelId: newMessage.channelId!,
userId: newMessage.userId!,
replies: origMessage
? {
messageId: newMessage.id,
repliesToId: origMessage?.id,
repliesToText: origMessage?.text,
}
: null,
};
} catch (error) {
return null;
}
}

View File

@@ -0,0 +1,90 @@
import { readonly } from "zod";
const EVENTS = {
NEW_CHANNEL_MESSAGE: "new_channel_message",
DELETE_CHANNEL_MESSAGE: "delete_channel_message",
};
export async function sendMessageToChannel(
instanceId: string,
categoryId: string,
channelId: string,
message: any,
event: string,
io: any,
): Promise<string | boolean> {
try {
//TODO: implement middleware to replace this
if (EVENTS.NEW_CHANNEL_MESSAGE === event) {
throw new Error("Event not implemented");
}
//TODO: add prisma to save channel message to DB
return new Promise((resolve) => {
io.to(instanceId).emit(event, message, (ack: any) => {
if (ack && ack.status === "received") {
console.log(`Message ${ack.messageId} acknowledged by client.`);
resolve(true);
} else {
console.log(
"services::realtime::sendMessageToChannel No acknowledgment received from client.",
);
resolve("no acknowledgment");
}
});
});
} catch (err) {
const errMessage = err as Error;
if (errMessage.message === "Event not implemented") {
console.log(
`services::realtime::sendMessageToChannel - Event not implemented. Attempted event: ${event}`,
);
return "event not implemented";
}
console.log("services::realtime::sendMessageToChannel - ", errMessage);
return false;
}
}
export async function removeMessageFromChannel(
instanceId: string,
categoryId: string,
channelId: string,
messageId: string,
event: string,
io: any,
): Promise<string | boolean> {
try {
//TODO: implement middleware to replace this
if (EVENTS.DELETE_CHANNEL_MESSAGE === event) {
throw new Error("event not implemented");
}
//TODO: add prisma to flag a channel message as deleted
return new Promise((resolve) => {
io.to(instanceId).emit(event, { messageId }, (ack: any) => {
if (ack && ack.status === "received") {
console.log(`Message ${ack.messageId} acknowledged by client.`);
resolve(true);
} else {
console.log(
"services::realtime::deleteMessageFromChannel No acknowledgment received from client.",
);
resolve("no acknowledgment");
}
});
});
} catch (err) {
const errMessage = err as Error;
if (errMessage.message === "Event not implemented") {
console.log(
`services::realtime::deleteMessageFromChannel - Event not implemented. Attempted event: ${event}`,
);
return false;
}
console.log("services::realtime::deleteMessageFromChannel - ", errMessage);
return false;
}
}

View File

@@ -0,0 +1,297 @@
import {
Message,
MessagePing,
PrismaClient,
Role,
UserAuth,
} from "@prisma/client";
import { CreateUserInput } from "../validators/userValidator";
import shaHash from "../helper/hashing";
const prisma = new PrismaClient();
export async function createUser(data: CreateUserInput): Promise<{
username: string;
nickname: string | null;
bio: string | null;
picture: string | null;
banner: string | null;
status: string;
admin: boolean;
} | null> {
const requestingUser = await getUserInformation(data.requestingUserId);
const requestingUserCredentials = await getUserCredentials(
data.requestingUserId,
);
if (
!requestingUser ||
!requestingUserCredentials ||
!requestingUser.admin ||
requestingUserCredentials.token == null ||
data.requestingUserToken != requestingUserCredentials.token
) {
return null;
}
if ((await prisma.user.count({ where: { username: data.username } })) >= 1) {
return null;
}
const userData = await prisma.user.create({
data: {
username: data.username,
nickname: data.nickname,
bio: data.bio,
picture: data.picture,
banner: data.banner,
status: data.status,
admin: data.admin,
},
});
if (
!(await prisma.userAuth.create({
data: {
userId: userData.id,
password: shaHash(data.passwordhash, userData.id),
token: null,
},
}))
) {
return null;
}
return userData;
}
export async function getUserCredentials(userId: string): Promise<{
userId: string;
password: string;
token: string | null;
} | null> {
try {
if (!userId) {
throw new Error("missing userId");
}
const userAuth = await prisma.userAuth.findUnique({
where: {
userId: userId,
},
});
if (!userAuth) {
throw new Error("could not find user credentials");
}
return {
userId: userAuth.userId,
password: userAuth.password,
token: userAuth.token,
};
} catch (err) {
const errMessage = err as Error;
if (errMessage.message === "missing userId") {
console.log("services::actions::getUserCredentials - missing userId");
return null;
}
if (errMessage.message === "could not find user credentials") {
console.log(
"services::actions::getUserCredentials - unable to find user credentials",
);
return null;
}
console.log(
"services::actions::getUserCredentials - unknown error",
errMessage,
);
return null;
}
}
export async function getUserInformation(userId: string): Promise<{
id: string;
userName: string;
nickName: string | null;
bio: string | null;
picture: string | null;
banner: string | null;
admin: boolean;
status: "online" | "offline" | "dnd" | "idle" | "invis";
role: Role[];
} | null> {
try {
if (!userId) {
throw new Error("missing userId");
}
const user = await prisma.user.findUnique({
where: {
id: userId,
},
});
if (!user) {
throw new Error("could not find user");
}
const userRoles = await prisma.role.findMany({
where: {
userId: userId,
},
});
return {
id: userId,
userName: user.username,
nickName: user.nickname,
bio: user.bio,
picture: user.picture,
banner: user.banner,
admin: user.admin,
status: (["online", "offline", "dnd", "idle", "invis"] as const).includes(
user.status as any,
)
? (user.status as "online" | "offline" | "dnd" | "idle" | "invis")
: "offline",
role: userRoles,
};
} catch (err) {
const errMessage = err as Error;
if (errMessage.message === "missing userId") {
console.log("services::actions::getUserInformation - missing userId");
return null;
}
if (errMessage.message === "could not find user") {
console.log(
"services::actions::getUserInformation - unable to find user",
);
return null;
}
console.log(
"services::actions::getUserInformation - unknown error",
errMessage,
);
return null;
}
}
export async function getAllUsersFrom(instanceId: string): Promise<
| {
id: string;
userName: string;
nickName: string | null;
bio: string | null;
picture: string | null;
banner: string | null;
admin: boolean;
status: "online" | "offline" | "dnd" | "idle" | "invis";
role: {
userId: string;
instanceId: string;
}[];
}[]
| null
> {
try {
const instances = await prisma.instance.count({
where: {
id: instanceId,
},
});
if (instances < 1) {
throw new Error("could not find given instance id");
}
const users = await prisma.user.findMany({
where: {
Role: {
some: {
instanceId: instanceId,
},
},
},
});
if (!users) {
throw new Error("could not get all users in instance");
}
const admins = await prisma.user.findMany({
where: {
admin: true,
},
});
if (!admins) {
throw new Error("could not get all admins");
}
const adminData = await Promise.all(
admins.map(async (u) => {
const adminRoles = await prisma.role.findMany({
where: {
userId: u.id,
},
});
if (!adminRoles) {
throw new Error("could not get admin roles for admin " + u.id);
}
return {
id: u.id,
userName: u.username,
nickName: u.nickname,
bio: u.bio,
picture: u.picture,
banner: u.banner,
admin: u.admin,
status: (
["online", "offline", "dnd", "idle", "invis"] as const
).includes(u.status as any)
? (u.status as "online" | "offline" | "dnd" | "idle" | "invis")
: "offline",
role: adminRoles,
};
}),
);
const userData = await Promise.all(
users.map(async (u) => {
const userRoles = await prisma.role.findMany({
where: {
userId: u.id,
},
});
if (!userRoles) {
throw new Error("could not get user roles for user " + u.id);
}
return {
id: u.id,
userName: u.username,
nickName: u.nickname,
bio: u.bio,
picture: u.picture,
banner: u.banner,
admin: u.admin,
status: (
["online", "offline", "dnd", "idle", "invis"] as const
).includes(u.status as any)
? (u.status as "online" | "offline" | "dnd" | "idle" | "invis")
: "offline",
role: userRoles,
};
}),
);
return userData.concat(adminData);
} catch (err) {
console.log(err);
return null;
}
}

View File

@@ -0,0 +1,61 @@
import { z } from "zod";
//category validators
export const createCategorySchema = z.object({
name: z.string().min(1).max(50),
position: z.number().min(0),
instanceId: z.uuidv7().optional(),
admin: z.boolean(),
requestingUserId: z.uuidv7(),
requestingUserToken: z.uuidv4(),
});
export const getCategorySchema = z.object({
id: z.uuidv7(),
});
export const getCategoriesByInstanceIdSchema = z.object({
instanceId: z.uuidv7(),
});
export const updateCategorySchema = z.object({
id: z.uuidv7(),
name: z.string().min(1).max(50).optional(),
position: z.number().min(0).optional(),
channels: z
.array(
z.object({
id: z.string(),
}),
)
.optional(),
admin: z.boolean(),
requestingUserId: z.uuidv7(),
requestingUserToken: z.uuidv4(),
});
export const deleteCategorySchema = z.object({
id: z.uuidv7(),
admin: z.boolean(),
requestingUserId: z.uuidv7(),
requestingUserToken: z.uuidv4(),
});
export const deleteCategoriesByInstanceIdSchema = z.object({
instanceId: z.uuidv7(),
admin: z.boolean(),
requestingUserId: z.uuidv7(),
requestingUserToken: z.uuidv4(),
});
export type CreateCategoryInput = z.infer<typeof createCategorySchema>;
export type GetCategoryInput = z.infer<typeof getCategorySchema>;
export type GetCategoriesByInstanceIdInput = z.infer<
typeof getCategoriesByInstanceIdSchema
>;
export type UpdateCategoryInput = z.infer<typeof updateCategorySchema>;
export type DeleteCategoryInput = z.infer<typeof deleteCategorySchema>;
export type DeleteCategoriesByInstanceIdInput = z.infer<
typeof deleteCategoriesByInstanceIdSchema
>;

View File

@@ -0,0 +1,56 @@
import { z } from "zod";
//channel validators
export const createChannelSchema = z.object({
type: z.enum(["text", "voice"]),
name: z.string().min(1).max(50),
description: z.string().max(255),
categoryId: z.uuidv7().optional(),
admin: z.boolean(),
requestingUserId: z.uuidv7(),
requestingUserToken: z.uuidv4(),
});
export const getChannelSchema = z.object({
id: z.uuidv7(),
});
export const getChannelsByCategoryIdSchema = z.object({
categoryId: z.uuidv7(),
});
export const updateChannelSchema = z.object({
id: z.uuidv7(),
name: z.string().min(1).max(50).optional(),
description: z.string().max(255).optional(),
categoryId: z.uuidv7().optional(),
admin: z.boolean(),
requestingUserId: z.uuidv7(),
requestingUserToken: z.uuidv4(),
});
export const deleteChannelSchema = z.object({
id: z.uuidv7(),
admin: z.boolean(),
requestingUserId: z.uuidv7(),
requestingUserToken: z.uuidv4(),
});
export const deleteChannelsByCategoryIdSchema = z.object({
categoryId: z.uuidv7(),
admin: z.boolean(),
requestingUserId: z.uuidv7(),
requestingUserToken: z.uuidv4(),
});
export type CreateChannelInput = z.infer<typeof createChannelSchema>;
export type GetChannelInput = z.infer<typeof getChannelSchema>;
export type GetChannelsByCategoryIdInput = z.infer<
typeof getChannelsByCategoryIdSchema
>;
export type UpdateChannelInput = z.infer<typeof updateChannelSchema>;
export type DeleteChannelInput = z.infer<typeof deleteChannelSchema>;
export type DeleteChannelsByCategoryIdInput = z.infer<
typeof deleteChannelsByCategoryIdSchema
>;

View File

@@ -0,0 +1,33 @@
import { z } from "zod";
export const createInstanceRequestSchema = z.object({
name: z.string().min(1, "Instance name cannot be empty"),
icon: z.url().optional(),
requestingUserId: z.uuidv7(),
requestingUserToken: z.string(),
});
export const getAllInstancesResponseSchema = z.object({
success: z.boolean(),
data: z
.array(
z.object({
id: z.string(),
name: z.string(),
icon: z.string().nullable(),
createdAt: z.string().refine((val) => !isNaN(Date.parse(val)), {
message: "Invalid date string format",
}),
updatedAt: z.string().refine((val) => !isNaN(Date.parse(val)), {
message: "Invalid date string format",
}),
}),
)
.optional(),
error: z.string().optional(),
});
export type CreateInstanceRequest = z.infer<typeof createInstanceRequestSchema>;
export type GetAllInstancesResponse = z.infer<
typeof getAllInstancesResponseSchema
>;

View File

@@ -0,0 +1,20 @@
import { z } from "zod";
export const getMessageByIdSchema = z.object({
id: z.uuidv7(),
});
export const getMessagesBeforeDate = z.object({
date: z.string().refine((val) => !isNaN(Date.parse(val)), {
message: "Invalid date string format",
}),
channelId: z.uuidv7(),
});
export const sendMessageSchema = z.object({
channelId: z.uuidv7(),
userId: z.uuidv7(),
content: z.string(),
token: z.string(),
repliedMessageId: z.uuidv7().nullable().optional(),
});

View File

@@ -0,0 +1,30 @@
import { z } from "zod";
export const queryUserByIdSchema = z.object({
id: z.uuidv7(),
});
export const queryAllUsersByInstanceId = z.object({
instanceId: z.uuidv7(),
});
import { is } from "zod/v4/locales";
export const createUserSchema = z.object({
username: z.string().min(3).max(30),
nickname: z.string().min(1).max(30).optional(),
bio: z.string().max(500).optional(),
picture: z.url().optional(),
banner: z.url().optional(),
status: z
.enum(["online", "offline", "dnd", "idle", "invis"])
.default("online"),
admin: z.boolean().default(false),
requestingUserId: z.uuidv7(),
requestingUserToken: z.uuidv4(),
passwordhash: z.string(),
});
export type QueryUserByIdInput = z.infer<typeof queryUserByIdSchema>;
export type QueryAllUsersByInstanceIdInput = z.infer<
typeof queryAllUsersByInstanceId
>;
export type CreateUserInput = z.infer<typeof createUserSchema>;