From b76e586bbccd1117ec4f0bedfb1b986ca14d2e13 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 22 Jan 2026 02:44:46 -0800 Subject: [PATCH] feat: Implement core API endpoints, database schema, ActivityPub federation, and initial user interface for the application. --- LICENSE | 201 +++ README.md | 118 +- drizzle.config.ts | 10 + package-lock.json | 1400 ++++++++++++++++- package.json | 25 +- public/favicon.png | Bin 0 -> 8480 bytes public/logo.svg | 1 + public/manifest.json | 36 + src/app/.well-known/nodeinfo/route.ts | 16 + src/app/.well-known/synapsis-handles/route.ts | 56 + src/app/.well-known/webfinger/route.ts | 56 + src/app/@[handle]/default.tsx | 3 + src/app/@[handle]/page.tsx | 638 ++++++++ src/app/admin/page.tsx | 385 +++++ src/app/api/admin/me/route.ts | 29 + src/app/api/admin/posts/[id]/route.ts | 69 + src/app/api/admin/posts/route.ts | 55 + src/app/api/admin/reports/[id]/route.ts | 55 + src/app/api/admin/reports/route.ts | 97 ++ src/app/api/admin/users/[id]/route.ts | 133 ++ src/app/api/admin/users/route.ts | 40 + src/app/api/auth/login/route.ts | 43 + src/app/api/auth/logout/route.ts | 16 + src/app/api/auth/me/route.ts | 113 ++ src/app/api/auth/register/route.ts | 50 + src/app/api/federation/gossip/route.ts | 60 + src/app/api/federation/handles/route.ts | 33 + src/app/api/handles/resolve/route.ts | 69 + src/app/api/install/status/route.ts | 54 + src/app/api/media/upload/route.ts | 82 + src/app/api/notifications/route.ts | 103 ++ src/app/api/posts/[id]/like/route.ts | 122 ++ src/app/api/posts/[id]/repost/route.ts | 68 + src/app/api/posts/route.ts | 319 ++++ src/app/api/reports/route.ts | 65 + src/app/api/search/route.ts | 86 + src/app/api/users/[handle]/follow/route.ts | 191 +++ src/app/api/users/[handle]/followers/route.ts | 54 + src/app/api/users/[handle]/following/route.ts | 54 + src/app/api/users/[handle]/posts/route.ts | 53 + src/app/api/users/[handle]/route.ts | 73 + src/app/favicon.ico | Bin 25931 -> 0 bytes src/app/globals.css | 1130 ++++++++++++- src/app/install/page.tsx | 271 ++++ src/app/layout.tsx | 33 +- src/app/login/page.tsx | 250 +++ src/app/nodeinfo/2.1/route.ts | 36 + src/app/notifications/page.tsx | 167 ++ src/app/page.tsx | 634 +++++++- src/app/search/page.tsx | 375 +++++ src/db/index.ts | 17 + src/db/schema.ts | 325 ++++ src/lib/activitypub/activities.ts | 202 +++ src/lib/activitypub/actor.ts | 119 ++ src/lib/activitypub/inbox.ts | 197 +++ src/lib/activitypub/index.ts | 7 + src/lib/activitypub/outbox.ts | 105 ++ src/lib/activitypub/signatures.ts | 165 ++ src/lib/activitypub/webfinger.ts | 113 ++ src/lib/auth/admin.ts | 28 + src/lib/auth/index.ts | 199 +++ src/lib/federation/handles.ts | 58 + 62 files changed, 9447 insertions(+), 115 deletions(-) create mode 100644 LICENSE create mode 100644 drizzle.config.ts create mode 100644 public/favicon.png create mode 100644 public/logo.svg create mode 100644 public/manifest.json create mode 100644 src/app/.well-known/nodeinfo/route.ts create mode 100644 src/app/.well-known/synapsis-handles/route.ts create mode 100644 src/app/.well-known/webfinger/route.ts create mode 100644 src/app/@[handle]/default.tsx create mode 100644 src/app/@[handle]/page.tsx create mode 100644 src/app/admin/page.tsx create mode 100644 src/app/api/admin/me/route.ts create mode 100644 src/app/api/admin/posts/[id]/route.ts create mode 100644 src/app/api/admin/posts/route.ts create mode 100644 src/app/api/admin/reports/[id]/route.ts create mode 100644 src/app/api/admin/reports/route.ts create mode 100644 src/app/api/admin/users/[id]/route.ts create mode 100644 src/app/api/admin/users/route.ts create mode 100644 src/app/api/auth/login/route.ts create mode 100644 src/app/api/auth/logout/route.ts create mode 100644 src/app/api/auth/me/route.ts create mode 100644 src/app/api/auth/register/route.ts create mode 100644 src/app/api/federation/gossip/route.ts create mode 100644 src/app/api/federation/handles/route.ts create mode 100644 src/app/api/handles/resolve/route.ts create mode 100644 src/app/api/install/status/route.ts create mode 100644 src/app/api/media/upload/route.ts create mode 100644 src/app/api/notifications/route.ts create mode 100644 src/app/api/posts/[id]/like/route.ts create mode 100644 src/app/api/posts/[id]/repost/route.ts create mode 100644 src/app/api/posts/route.ts create mode 100644 src/app/api/reports/route.ts create mode 100644 src/app/api/search/route.ts create mode 100644 src/app/api/users/[handle]/follow/route.ts create mode 100644 src/app/api/users/[handle]/followers/route.ts create mode 100644 src/app/api/users/[handle]/following/route.ts create mode 100644 src/app/api/users/[handle]/posts/route.ts create mode 100644 src/app/api/users/[handle]/route.ts delete mode 100644 src/app/favicon.ico create mode 100644 src/app/install/page.tsx create mode 100644 src/app/login/page.tsx create mode 100644 src/app/nodeinfo/2.1/route.ts create mode 100644 src/app/notifications/page.tsx create mode 100644 src/app/search/page.tsx create mode 100644 src/db/index.ts create mode 100644 src/db/schema.ts create mode 100644 src/lib/activitypub/activities.ts create mode 100644 src/lib/activitypub/actor.ts create mode 100644 src/lib/activitypub/inbox.ts create mode 100644 src/lib/activitypub/index.ts create mode 100644 src/lib/activitypub/outbox.ts create mode 100644 src/lib/activitypub/signatures.ts create mode 100644 src/lib/activitypub/webfinger.ts create mode 100644 src/lib/auth/admin.ts create mode 100644 src/lib/auth/index.ts create mode 100644 src/lib/federation/handles.ts diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index e215bc4..7f57ae7 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,108 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# Project Synapsis -## Getting Started +An open-source, federated social network designed as global communication infrastructure. -First, run the development server: +## Features -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` +- **Federation**: ActivityPub-compatible, interoperable with Mastodon and other fediverse platforms +- **Global Identity**: Portable identity backed by DIDs - your handle works everywhere +- **Easy Deployment**: Deploy to Vercel with one click, no Docker or complex setup required +- **Modern UX**: Clean, Vercel-inspired dark theme with responsive design +- **Transparent Feeds**: Chronological and curated feeds with clear algorithms +- **Moderation**: Admin dashboard for reports, post removals, and user actions -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +## Quick Start -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +### Prerequisites -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +- Node.js 18+ +- A Neon PostgreSQL database (free tier available at https://neon.tech) +- An Upstash Redis database (free tier available at https://upstash.com) -## Learn More +### Setup -To learn more about Next.js, take a look at the following resources: +1. Clone the repository: + ```bash + git clone https://github.com/your-username/synapsis.git + cd synapsis + ``` -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +2. Install dependencies: + ```bash + npm install + ``` -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +3. Copy the environment example and configure: + ```bash + cp .env.example .env + ``` -## Deploy on Vercel +4. Update `.env` with your database credentials: + ``` + DATABASE_URL=postgresql://... + UPSTASH_REDIS_REST_URL=https://... + UPSTASH_REDIS_REST_TOKEN=... + AUTH_SECRET=generate-a-random-string + NEXT_PUBLIC_NODE_DOMAIN=your-domain.com + NEXT_PUBLIC_NODE_NAME=My Synapsis Node + ADMIN_EMAILS=comma,separated,emails + ``` -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +5. Run database migrations: + ```bash + npm run db:push + ``` -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +6. Start the development server: + ```bash + npm run dev + ``` + +### Deploy to Vercel + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/your-username/synapsis) + +1. Click the button above +2. Connect your Neon PostgreSQL and Upstash Redis +3. Configure environment variables +4. Deploy! + +## Architecture + +- **Frontend**: Next.js 14+ with App Router +- **Backend**: Next.js API Routes (serverless) +- **Database**: PostgreSQL via Neon +- **Cache**: Redis via Upstash +- **Federation**: ActivityPub protocol + +## API Endpoints + +### Authentication +- `POST /api/auth/register` - Create account +- `POST /api/auth/login` - Login +- `POST /api/auth/logout` - Logout +- `GET /api/auth/me` - Get current user + +### Posts +- `POST /api/posts` - Create a post +- `GET /api/posts` - Get feed +- `POST /api/posts/[id]/like` - Like a post +- `DELETE /api/posts/[id]/like` - Unlike a post +- `POST /api/posts/[id]/repost` - Repost + +### Federation +- `GET /.well-known/webfinger` - WebFinger discovery +- `GET /.well-known/nodeinfo` - NodeInfo discovery +- `GET /nodeinfo/2.1` - Node metadata +- `GET /.well-known/synapsis-handles` - Handle registry export +- `POST /api/federation/handles` - Ingest handle registry from peers +- `POST /api/federation/gossip` - Pull registry from peer nodes (admin) +- `GET /api/handles/resolve` - Resolve handle to DID + +### Installation +- `GET /install` - Setup wizard +- `GET /api/install/status` - Installation status + +## License + +Apache 2.0 License - see LICENSE file for details. diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..654e617 --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + schema: './src/db/schema.ts', + out: './drizzle', + dialect: 'postgresql', + dbCredentials: { + url: process.env.DATABASE_URL!, + }, +}); diff --git a/package-lock.json b/package-lock.json index 81f67b5..b29041f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,15 +8,27 @@ "name": "synapsis", "version": "0.1.0", "dependencies": { + "@neondatabase/serverless": "^0.10.4", + "@upstash/redis": "^1.34.3", + "bcryptjs": "^2.4.3", + "crypto-js": "^4.2.0", + "drizzle-orm": "^0.44.1", + "jose": "^6.0.11", "next": "16.1.4", + "next-auth": "^5.0.0-beta.25", "react": "19.2.3", - "react-dom": "19.2.3" + "react-dom": "19.2.3", + "uuid": "^11.1.0", + "zod": "^4.3.5" }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@types/bcryptjs": "^2.4.6", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@types/uuid": "^10.0.0", + "drizzle-kit": "^0.31.8", "eslint": "^9", "eslint-config-next": "16.1.4", "tailwindcss": "^4", @@ -36,6 +48,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@auth/core": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.0.tgz", + "integrity": "sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ==", + "license": "ISC", + "dependencies": { + "@panva/hkdf": "^1.2.1", + "jose": "^6.0.6", + "oauth4webapi": "^3.3.0", + "preact": "10.24.3", + "preact-render-to-string": "6.5.11" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "nodemailer": "^6.8.0" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/@babel/code-frame": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", @@ -277,6 +318,13 @@ "node": ">=6.9.0" } }, + "node_modules/@drizzle-team/brocli": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@emnapi/core": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", @@ -310,6 +358,884 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild-kit/core-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.18.20", + "source-map-support": "^0.5.21" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/@esbuild-kit/esm-loader": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "@esbuild-kit/core-utils": "^3.3.2", + "get-tsconfig": "^4.7.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -1035,6 +1961,16 @@ "@tybys/wasm-util": "^0.10.0" } }, + "node_modules/@neondatabase/serverless": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.10.4.tgz", + "integrity": "sha512-2nZuh3VUO9voBauuh+IGYRhGU/MskWHt1IuZvHcJw6GLjDgtqj/KViKo7SIrLdGLdot7vFbiRRw+BgEy3wT9HA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/pg": "8.11.6" + } + }, "node_modules/@next/env": { "version": "16.1.4", "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.4.tgz", @@ -1227,6 +2163,15 @@ "node": ">=12.4.0" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1525,6 +2470,13 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1550,12 +2502,22 @@ "version": "20.19.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/@types/pg": { + "version": "8.11.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.6.tgz", + "integrity": "sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^4.0.1" + } + }, "node_modules/@types/react": { "version": "19.2.9", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz", @@ -1577,6 +2539,13 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.53.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", @@ -2116,6 +3085,16 @@ "win32" ] }, + "node_modules/@upstash/redis": { + "version": "1.36.1", + "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.36.1.tgz", + "integrity": "sha512-N6SjDcgXdOcTAF+7uNoY69o7hCspe9BcA7YjQdxVu5d25avljTwyLaHBW3krWjrP0FfocgMk94qyVtQbeDp39A==", + "license": "MIT", + "peer": true, + "dependencies": { + "uncrypto": "^0.1.3" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -2419,6 +3398,12 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -2478,6 +3463,13 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -2630,6 +3622,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -2782,6 +3780,147 @@ "node": ">=0.10.0" } }, + "node_modules/drizzle-kit": { + "version": "0.31.8", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.8.tgz", + "integrity": "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@drizzle-team/brocli": "^0.10.2", + "@esbuild-kit/esm-loader": "^2.5.5", + "esbuild": "^0.25.4", + "esbuild-register": "^3.5.0" + }, + "bin": { + "drizzle-kit": "bin.cjs" + } + }, + "node_modules/drizzle-orm": { + "version": "0.44.7", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.44.7.tgz", + "integrity": "sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1.13", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/sql.js": "*", + "@upstash/redis": ">=1.34.7", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "gel": ">=2", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "gel": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3002,6 +4141,62 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3217,6 +4412,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -4405,6 +5601,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5007,6 +6212,33 @@ } } }, + "node_modules/next-auth": { + "version": "5.0.0-beta.30", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.30.tgz", + "integrity": "sha512-+c51gquM3F6nMVmoAusRJ7RIoY0K4Ts9HCCwyy/BRoe4mp3msZpOzYMyb5LAYc1wSo74PMQkGDcaghIO7W6Xjg==", + "license": "ISC", + "dependencies": { + "@auth/core": "0.41.0" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "next": "^14.0.0-0 || ^15.0.0 || ^16.0.0", + "nodemailer": "^7.0.7", + "react": "^18.2.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -5042,6 +6274,15 @@ "dev": true, "license": "MIT" }, + "node_modules/oauth4webapi": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.3.tgz", + "integrity": "sha512-pQ5BsX3QRTgnt5HxgHwgunIRaDXBdkT23tf8dfzmtTIL2LTpdmxgbpbBm0VgFWAIDlezQvQCTgnVIUmHupXHxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5165,6 +6406,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5273,6 +6520,48 @@ "dev": true, "license": "MIT" }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-protocol": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.11.0.tgz", + "integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.1.0.tgz", + "integrity": "sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.1.0", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5331,6 +6620,71 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "license": "MIT", + "dependencies": { + "obuf": "~1.1.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postgres-date": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz", + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-range": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz", + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==", + "license": "MIT" + }, + "node_modules/preact": { + "version": "10.24.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", + "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz", + "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==", + "license": "MIT", + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5811,6 +7165,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5820,6 +7184,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -6303,11 +7678,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/unrs-resolver": { @@ -6386,6 +7766,19 @@ "punycode": "^2.1.0" } }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6525,7 +7918,6 @@ "version": "4.3.5", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", - "dev": true, "license": "MIT", "peer": true, "funding": { diff --git a/package.json b/package.json index d61b253..9561051 100644 --- a/package.json +++ b/package.json @@ -2,25 +2,42 @@ "name": "synapsis", "version": "0.1.0", "private": true, + "license": "Apache-2.0", "scripts": { - "dev": "next dev", + "dev": "next dev --turbopack", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "db:generate": "drizzle-kit generate", + "db:push": "drizzle-kit push", + "db:studio": "drizzle-kit studio", + "type-check": "tsc --noEmit" }, "dependencies": { + "@neondatabase/serverless": "^0.10.4", + "@upstash/redis": "^1.34.3", + "bcryptjs": "^2.4.3", + "crypto-js": "^4.2.0", + "drizzle-orm": "^0.44.1", + "jose": "^6.0.11", "next": "16.1.4", + "next-auth": "^5.0.0-beta.25", "react": "19.2.3", - "react-dom": "19.2.3" + "react-dom": "19.2.3", + "uuid": "^11.1.0", + "zod": "^4.3.5" }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@types/bcryptjs": "^2.4.6", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@types/uuid": "^10.0.0", + "drizzle-kit": "^0.31.8", "eslint": "^9", "eslint-config-next": "16.1.4", "tailwindcss": "^4", "typescript": "^5" } -} +} \ No newline at end of file diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..d12f44ea303cd1378fe624d8641188c829d8140c GIT binary patch literal 8480 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 \ No newline at end of file diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..0714de7 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,36 @@ +{ + "name": "Synapsis", + "short_name": "Synapsis", + "description": "Federated social network infrastructure", + "start_url": "/", + "display": "standalone", + "background_color": "#0a0a0a", + "theme_color": "#00d4aa", + "orientation": "portrait-primary", + "icons": [ + { + "src": "/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + } + ], + "categories": [ + "social", + "communication" + ], + "screenshots": [], + "shortcuts": [ + { + "name": "Compose", + "url": "/?compose=true", + "description": "Write a new post" + } + ] +} \ No newline at end of file diff --git a/src/app/.well-known/nodeinfo/route.ts b/src/app/.well-known/nodeinfo/route.ts new file mode 100644 index 0000000..4eabd3e --- /dev/null +++ b/src/app/.well-known/nodeinfo/route.ts @@ -0,0 +1,16 @@ +import { NextResponse } from 'next/server'; + +export async function GET() { + const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000'; + const nodeName = process.env.NEXT_PUBLIC_NODE_NAME || 'Synapsis Node'; + const nodeDescription = process.env.NEXT_PUBLIC_NODE_DESCRIPTION || 'A Synapsis federated social network node'; + + return NextResponse.json({ + links: [ + { + rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1', + href: `https://${nodeDomain}/nodeinfo/2.1`, + }, + ], + }); +} diff --git a/src/app/.well-known/synapsis-handles/route.ts b/src/app/.well-known/synapsis-handles/route.ts new file mode 100644 index 0000000..1e3a355 --- /dev/null +++ b/src/app/.well-known/synapsis-handles/route.ts @@ -0,0 +1,56 @@ +import { NextResponse } from 'next/server'; +import { db, handleRegistry } from '@/db'; +import { desc, eq, gt } from 'drizzle-orm'; +import { normalizeHandle } from '@/lib/federation/handles'; + +export async function GET(request: Request) { + try { + if (!db) { + return NextResponse.json({ handles: [] }); + } + + const { searchParams } = new URL(request.url); + const handleParam = searchParams.get('handle'); + const sinceParam = searchParams.get('since'); + const limit = Math.min(parseInt(searchParams.get('limit') || '100'), 500); + + if (handleParam) { + const cleanHandle = normalizeHandle(handleParam); + const entry = await db.query.handleRegistry.findFirst({ + where: eq(handleRegistry.handle, cleanHandle), + }); + + if (!entry) { + return NextResponse.json({ handles: [] }); + } + + return NextResponse.json({ + handles: [{ + handle: entry.handle, + did: entry.did, + nodeDomain: entry.nodeDomain, + updatedAt: entry.updatedAt, + }], + }); + } + + const sinceDate = sinceParam ? new Date(sinceParam) : null; + const entries = await db.query.handleRegistry.findMany({ + where: sinceDate ? gt(handleRegistry.updatedAt, sinceDate) : undefined, + orderBy: [desc(handleRegistry.updatedAt)], + limit, + }); + + return NextResponse.json({ + handles: entries.map((entry) => ({ + handle: entry.handle, + did: entry.did, + nodeDomain: entry.nodeDomain, + updatedAt: entry.updatedAt, + })), + }); + } catch (error) { + console.error('Handle export error:', error); + return NextResponse.json({ error: 'Failed to export handles' }, { status: 500 }); + } +} diff --git a/src/app/.well-known/webfinger/route.ts b/src/app/.well-known/webfinger/route.ts new file mode 100644 index 0000000..b5e1ffa --- /dev/null +++ b/src/app/.well-known/webfinger/route.ts @@ -0,0 +1,56 @@ +import { NextResponse } from 'next/server'; +import { db, users } from '@/db'; +import { eq } from 'drizzle-orm'; +import { generateWebFingerResponse, parseWebFingerResource } from '@/lib/activitypub/webfinger'; + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + const resource = searchParams.get('resource'); + + if (!resource) { + return NextResponse.json( + { error: 'Missing resource parameter' }, + { status: 400 } + ); + } + + const parsed = parseWebFingerResource(resource); + + if (!parsed) { + return NextResponse.json( + { error: 'Invalid resource format' }, + { status: 400 } + ); + } + + const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000'; + + // Check if this is our domain + if (parsed.domain !== nodeDomain && parsed.domain !== nodeDomain.replace(/:\d+$/, '')) { + return NextResponse.json( + { error: 'Resource not found' }, + { status: 404 } + ); + } + + // Find the user + const user = await db.query.users.findFirst({ + where: eq(users.handle, parsed.handle.toLowerCase()), + }); + + if (!user) { + return NextResponse.json( + { error: 'User not found' }, + { status: 404 } + ); + } + + const response = generateWebFingerResponse(user.handle, nodeDomain); + + return NextResponse.json(response, { + headers: { + 'Content-Type': 'application/jrd+json', + 'Access-Control-Allow-Origin': '*', + }, + }); +} diff --git a/src/app/@[handle]/default.tsx b/src/app/@[handle]/default.tsx new file mode 100644 index 0000000..6ddf1b7 --- /dev/null +++ b/src/app/@[handle]/default.tsx @@ -0,0 +1,3 @@ +export default function Default() { + return null; +} diff --git a/src/app/@[handle]/page.tsx b/src/app/@[handle]/page.tsx new file mode 100644 index 0000000..d0d1fe2 --- /dev/null +++ b/src/app/@[handle]/page.tsx @@ -0,0 +1,638 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { useParams } from 'next/navigation'; + +interface User { + id: string; + handle: string; + displayName: string; + bio?: string; + avatarUrl?: string; + headerUrl?: string; + followersCount: number; + followingCount: number; + postsCount: number; + createdAt: string; +} + +interface UserSummary { + id: string; + handle: string; + displayName?: string | null; + bio?: string | null; + avatarUrl?: string | null; +} + +interface MediaItem { + id: string; + url: string; + altText?: string | null; +} + +interface Post { + id: string; + content: string; + createdAt: string; + likesCount: number; + repostsCount: number; + repliesCount: number; + author: User; + media?: MediaItem[]; +} + +// Icons +const ArrowLeftIcon = () => ( + + + + +); + +const CalendarIcon = () => ( + + + + + + +); + +const HeartIcon = ({ filled }: { filled?: boolean }) => ( + + + +); + +const RepeatIcon = () => ( + + + + + + +); + +const MessageIcon = () => ( + + + +); + +const FlagIcon = () => ( + + + + +); + +function UserRow({ user }: { user: UserSummary }) { + return ( + +
+ {user.avatarUrl ? ( + {user.displayName + ) : ( + (user.displayName || user.handle).charAt(0).toUpperCase() + )} +
+
+
{user.displayName || user.handle}
+
@{user.handle}
+ {user.bio && ( +
{user.bio}
+ )} +
+ + ); +} + +function PostCard({ post }: { post: Post }) { + const [liked, setLiked] = useState(false); + const [reposted, setReposted] = useState(false); + const [reporting, setReporting] = useState(false); + + const formatTime = (dateStr: string) => { + const date = new Date(dateStr); + const now = new Date(); + const diff = now.getTime() - date.getTime(); + const minutes = Math.floor(diff / 60000); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + if (minutes < 1) return 'now'; + if (minutes < 60) return `${minutes}m`; + if (hours < 24) return `${hours}h`; + if (days < 7) return `${days}d`; + return date.toLocaleDateString(); + }; + + return ( +
+
+
+ {post.author.avatarUrl ? ( + {post.author.displayName} + ) : ( + post.author.displayName?.charAt(0).toUpperCase() || post.author.handle.charAt(0).toUpperCase() + )} +
+
+ + {post.author.displayName || post.author.handle} + + @{post.author.handle} ยท {formatTime(post.createdAt)} +
+
+
{post.content}
+ {post.media && post.media.length > 0 && ( +
+ {post.media.map((item) => ( +
+ {item.altText +
+ ))} +
+ )} +
+ + + + +
+
+ ); +} + +export default function ProfilePage() { + const params = useParams(); + const handle = (params.handle as string)?.replace(/^@/, '') || ''; + + const [user, setUser] = useState(null); + const [posts, setPosts] = useState([]); + const [currentUser, setCurrentUser] = useState<{ id: string; handle: string } | null>(null); + const [isFollowing, setIsFollowing] = useState(false); + const [loading, setLoading] = useState(true); + const [activeTab, setActiveTab] = useState<'posts' | 'followers' | 'following'>('posts'); + const [followers, setFollowers] = useState([]); + const [following, setFollowing] = useState([]); + const [followersLoading, setFollowersLoading] = useState(false); + const [followingLoading, setFollowingLoading] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const [profileForm, setProfileForm] = useState({ + displayName: '', + bio: '', + avatarUrl: '', + headerUrl: '', + }); + const [saveError, setSaveError] = useState(null); + const [isSaving, setIsSaving] = useState(false); + + useEffect(() => { + setIsEditing(false); + setSaveError(null); + setFollowers([]); + setFollowing([]); + // Get current user + fetch('/api/auth/me') + .then(res => res.json()) + .then(data => setCurrentUser(data.user)) + .catch(() => { }); + + // Get profile + fetch(`/api/users/${handle}`) + .then(res => res.json()) + .then(data => { + setUser(data.user); + setLoading(false); + }) + .catch(() => setLoading(false)); + + // Get posts + fetch(`/api/users/${handle}/posts`) + .then(res => res.json()) + .then(data => setPosts(data.posts || [])) + .catch(() => { }); + }, [handle]); + + useEffect(() => { + if (user && currentUser?.handle === user.handle) { + setProfileForm({ + displayName: user.displayName || '', + bio: user.bio || '', + avatarUrl: user.avatarUrl || '', + headerUrl: user.headerUrl || '', + }); + } + }, [user, currentUser]); + + useEffect(() => { + if (!currentUser || !user || currentUser.handle === user.handle) { + setIsFollowing(false); + return; + } + + fetch(`/api/users/${handle}/follow`) + .then(res => res.json()) + .then(data => setIsFollowing(!!data.following)) + .catch(() => setIsFollowing(false)); + }, [currentUser, user, handle]); + + useEffect(() => { + if (activeTab === 'followers') { + setFollowersLoading(true); + fetch(`/api/users/${handle}/followers`) + .then(res => res.json()) + .then(data => setFollowers(data.followers || [])) + .catch(() => setFollowers([])) + .finally(() => setFollowersLoading(false)); + } + + if (activeTab === 'following') { + setFollowingLoading(true); + fetch(`/api/users/${handle}/following`) + .then(res => res.json()) + .then(data => setFollowing(data.following || [])) + .catch(() => setFollowing([])) + .finally(() => setFollowingLoading(false)); + } + }, [activeTab, handle]); + + const handleFollow = async () => { + if (!currentUser) return; + + const method = isFollowing ? 'DELETE' : 'POST'; + const res = await fetch(`/api/users/${handle}/follow`, { method }); + + if (res.ok) { + setIsFollowing(!isFollowing); + if (user) { + setUser({ + ...user, + followersCount: isFollowing ? user.followersCount - 1 : user.followersCount + 1, + }); + } + } + }; + + const handleSaveProfile = async () => { + if (!isOwnProfile) return; + setIsSaving(true); + setSaveError(null); + + try { + const res = await fetch('/api/auth/me', { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(profileForm), + }); + + const data = await res.json(); + + if (!res.ok) { + throw new Error(data.error || 'Failed to update profile'); + } + + setUser(data.user); + setIsEditing(false); + } catch (error) { + console.error('Profile update failed', error); + setSaveError('Unable to update profile. Please try again.'); + } finally { + setIsSaving(false); + } + }; + + const formatDate = (dateStr: string) => { + return new Date(dateStr).toLocaleDateString('en-US', { + month: 'long', + year: 'numeric', + }); + }; + + if (loading) { + return ( +
+ Loading... +
+ ); + } + + if (!user) { + return ( +
+

User not found

+ Go home +
+ ); + } + + const isOwnProfile = currentUser?.handle === user.handle; + + return ( +
+ {/* Header */} +
+ + + +
+

{user.displayName || user.handle}

+

{user.postsCount} posts

+
+
+ + {/* Profile Header */} +
+ {/* Banner */} +
+ + {/* Avatar & Actions */} +
+
+
+ {user.avatarUrl ? ( + {user.displayName + ) : ( + (user.displayName || user.handle).charAt(0).toUpperCase() + )} +
+ + {!isOwnProfile && currentUser && ( + + )} + + {isOwnProfile && ( + + )} +
+ + {/* User Info */} +
+

{user.displayName || user.handle}

+

@{user.handle}

+ + {user.bio && ( +

{user.bio}

+ )} + +
+ + Joined {formatDate(user.createdAt)} +
+ +
+ + +
+
+
+ + {isOwnProfile && isEditing && ( +
+
+
Edit profile
+
+
+ + setProfileForm({ ...profileForm, displayName: e.target.value })} + maxLength={50} + /> +
+
+ +