Hey is a decentralized and permissionless social media app built with Lens Protocol 🌿

refactor: remove unused Prisma files and update environment configuration

yoginth.com 1dc382fc 4ceadbd7

verified
+2 -354
-1
README.md
··· 60 60 61 61 - `LENS_NETWORK` – Lens network to use (`mainnet`, `testnet`, or `staging`). 62 62 - `LENS_DATABASE_URL` – Read-only Postgres connection for Lens data. 63 - - `DATABASE_URL` – Postgres connection used by the Prisma backend. 64 63 - `PRIVATE_KEY` – Private key used to sign Lens requests. 65 64 - `EVER_ACCESS_KEY` – Access key for 4EVERLAND storage. 66 65 - `EVER_ACCESS_SECRET` – Secret key for 4EVERLAND storage.
-1
apps/api/.env.example
··· 1 1 LENS_NETWORK="testnet" 2 2 LENS_DATABASE_URL="" 3 - DATABASE_URL="" 4 3 PRIVATE_KEY="0x1d65a3183f35ecef73ce8f7d47920d58abdf3766debc2ff0b4c653b7633707fd" # Testnet private key without funds 5 4 EVER_ACCESS_KEY="" 6 5 EVER_ACCESS_SECRET=""
+1 -8
apps/api/package.json
··· 7 7 "build": "echo 'Not required'", 8 8 "dev": "tsx watch src/index.ts", 9 9 "start": "tsx watch src/index.ts", 10 - "typecheck": "tsc --pretty", 11 - "postinstall": "prisma generate", 12 - "prisma:generate": "prisma generate", 13 - "prisma:migrate": "prisma migrate dev", 14 - "prisma:reset": "prisma migrate reset --force", 15 - "prisma:format": "prisma format" 10 + "typecheck": "tsc --pretty" 16 11 }, 17 12 "dependencies": { 18 13 "@aws-sdk/client-sts": "^3.896.0", ··· 22 17 "@hey/indexer": "workspace:*", 23 18 "@hono/node-server": "^1.19.5", 24 19 "@hono/zod-validator": "^0.7.3", 25 - "@prisma/client": "^6.17.1", 26 20 "@lens-chain/sdk": "^1.0.3", 27 21 "dotenv": "^17.2.2", 28 22 "hono": "^4.9.9", ··· 38 32 "@hey/config": "workspace:*", 39 33 "@hey/types": "workspace:*", 40 34 "@types/node": "^24.5.2", 41 - "prisma": "^6.17.1", 42 35 "typescript": "^5.9.2" 43 36 } 44 37 }
-46
apps/api/prisma/migrations/20251020162124_init/migration.sql
··· 1 - -- CreateTable 2 - CREATE TABLE "LensAccount" ( 3 - "id" TEXT NOT NULL, 4 - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 5 - "updatedAt" TIMESTAMP(3) NOT NULL, 6 - 7 - CONSTRAINT "LensAccount_pkey" PRIMARY KEY ("id") 8 - ); 9 - 10 - -- CreateTable 11 - CREATE TABLE "ChatConversation" ( 12 - "id" TEXT NOT NULL, 13 - "key" TEXT NOT NULL, 14 - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 15 - "updatedAt" TIMESTAMP(3) NOT NULL, 16 - "senderId" TEXT NOT NULL, 17 - "receiverId" TEXT NOT NULL, 18 - 19 - CONSTRAINT "ChatConversation_pkey" PRIMARY KEY ("id") 20 - ); 21 - 22 - -- CreateTable 23 - CREATE TABLE "Message" ( 24 - "id" TEXT NOT NULL, 25 - "conversationId" TEXT NOT NULL, 26 - "senderId" TEXT NOT NULL, 27 - "content" TEXT NOT NULL, 28 - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 29 - 30 - CONSTRAINT "Message_pkey" PRIMARY KEY ("id") 31 - ); 32 - 33 - -- CreateIndex 34 - CREATE UNIQUE INDEX "ChatConversation_key_key" ON "ChatConversation"("key"); 35 - 36 - -- AddForeignKey 37 - ALTER TABLE "ChatConversation" ADD CONSTRAINT "ChatConversation_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "LensAccount"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 38 - 39 - -- AddForeignKey 40 - ALTER TABLE "ChatConversation" ADD CONSTRAINT "ChatConversation_receiverId_fkey" FOREIGN KEY ("receiverId") REFERENCES "LensAccount"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 41 - 42 - -- AddForeignKey 43 - ALTER TABLE "Message" ADD CONSTRAINT "Message_conversationId_fkey" FOREIGN KEY ("conversationId") REFERENCES "ChatConversation"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 44 - 45 - -- AddForeignKey 46 - ALTER TABLE "Message" ADD CONSTRAINT "Message_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "LensAccount"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-3
apps/api/prisma/migrations/migration_lock.toml
··· 1 - # Please do not edit this file manually 2 - # It should be added in your version-control system (e.g., Git) 3 - provider = "postgresql"
-39
apps/api/prisma/schema.prisma
··· 1 - generator client { 2 - provider = "prisma-client-js" 3 - } 4 - 5 - datasource db { 6 - provider = "postgresql" 7 - url = env("DATABASE_URL") 8 - } 9 - 10 - model LensAccount { 11 - id String @id 12 - createdAt DateTime @default(now()) 13 - updatedAt DateTime @updatedAt 14 - sentConversations ChatConversation[] @relation("ConversationSender") 15 - receivedConversations ChatConversation[] @relation("ConversationReceiver") 16 - messages Message[] @relation("AccountMessages") 17 - } 18 - 19 - model ChatConversation { 20 - id String @id @default(cuid()) 21 - key String @unique 22 - createdAt DateTime @default(now()) 23 - updatedAt DateTime @updatedAt 24 - sender LensAccount @relation("ConversationSender", fields: [senderId], references: [id]) 25 - senderId String 26 - receiver LensAccount @relation("ConversationReceiver", fields: [receiverId], references: [id]) 27 - receiverId String 28 - messages Message[] 29 - } 30 - 31 - model Message { 32 - id String @id @default(cuid()) 33 - conversation ChatConversation @relation(fields: [conversationId], references: [id]) 34 - conversationId String 35 - sender LensAccount @relation("AccountMessages", fields: [senderId], references: [id]) 36 - senderId String 37 - content String 38 - createdAt DateTime @default(now()) 39 - }
-2
apps/api/src/index.ts
··· 5 5 import { Hono } from "hono"; 6 6 import authContext from "./context/authContext"; 7 7 import cors from "./middlewares/cors"; 8 - import chatRouter from "./routes/chat"; 9 8 import cronRouter from "./routes/cron"; 10 9 import ensRouter from "./routes/ens"; 11 10 import metadataRouter from "./routes/metadata"; ··· 21 20 app.use(authContext); 22 21 23 22 app.get("/ping", ping); 24 - app.route("/chat", chatRouter); 25 23 app.route("/cron", cronRouter); 26 24 app.route("/metadata", metadataRouter); 27 25 app.route("/oembed", oembedRouter);
-20
apps/api/src/routes/chat/conversations/createConversation.ts
··· 1 - import { Status } from "@hey/data/enums"; 2 - import type { Context } from "hono"; 3 - import handleApiError from "@/utils/handleApiError"; 4 - import { chatService } from "./service"; 5 - 6 - const createConversation = async (ctx: Context) => { 7 - try { 8 - const { senderId, receiverId } = await ctx.req.json(); 9 - const conversation = await chatService.getOrCreateConversation( 10 - senderId, 11 - receiverId 12 - ); 13 - 14 - return ctx.json({ conversation, status: Status.Success }); 15 - } catch (error) { 16 - return handleApiError(ctx, error); 17 - } 18 - }; 19 - 20 - export default createConversation;
-22
apps/api/src/routes/chat/conversations/getConversation.ts
··· 1 - import { Status } from "@hey/data/enums"; 2 - import type { Context } from "hono"; 3 - import ApiError from "@/utils/apiError"; 4 - import handleApiError from "@/utils/handleApiError"; 5 - import { chatService } from "./service"; 6 - 7 - const getConversation = async (ctx: Context) => { 8 - try { 9 - const { conversationId } = ctx.req.param(); 10 - const conversation = await chatService.getConversation(conversationId); 11 - 12 - if (!conversation) { 13 - throw new ApiError(404, "Conversation not found."); 14 - } 15 - 16 - return ctx.json({ conversation, status: Status.Success }); 17 - } catch (error) { 18 - return handleApiError(ctx, error); 19 - } 20 - }; 21 - 22 - export default getConversation;
-35
apps/api/src/routes/chat/conversations/index.ts
··· 1 - import { zValidator } from "@hono/zod-validator"; 2 - import { Hono } from "hono"; 3 - import z from "zod"; 4 - import createConversation from "./createConversation"; 5 - import getConversation from "./getConversation"; 6 - import sendMessage from "./sendMessage"; 7 - 8 - const app = new Hono(); 9 - 10 - app.post( 11 - "/", 12 - zValidator( 13 - "json", 14 - z.object({ receiverId: z.string().min(1), senderId: z.string().min(1) }) 15 - ), 16 - createConversation 17 - ); 18 - 19 - app.get( 20 - "/:conversationId", 21 - zValidator("param", z.object({ conversationId: z.string().min(1) })), 22 - getConversation 23 - ); 24 - 25 - app.post( 26 - "/:conversationId/messages", 27 - zValidator("param", z.object({ conversationId: z.string().min(1) })), 28 - zValidator( 29 - "json", 30 - z.object({ content: z.string().min(1), senderId: z.string().min(1) }) 31 - ), 32 - sendMessage 33 - ); 34 - 35 - export default app;
-22
apps/api/src/routes/chat/conversations/sendMessage.ts
··· 1 - import { Status } from "@hey/data/enums"; 2 - import type { Context } from "hono"; 3 - import handleApiError from "@/utils/handleApiError"; 4 - import { chatService } from "./service"; 5 - 6 - const sendMessage = async (ctx: Context) => { 7 - try { 8 - const { conversationId } = ctx.req.param(); 9 - const { senderId, content } = await ctx.req.json(); 10 - const message = await chatService.sendMessage( 11 - conversationId, 12 - senderId, 13 - content 14 - ); 15 - 16 - return ctx.json({ message, status: Status.Success }); 17 - } catch (error) { 18 - return handleApiError(ctx, error); 19 - } 20 - }; 21 - 22 - export default sendMessage;
-121
apps/api/src/routes/chat/conversations/service.ts
··· 1 - import type { Prisma } from "@prisma/client"; 2 - import prisma from "@/utils/prisma"; 3 - 4 - const conversationInclude = { 5 - messages: { 6 - include: { 7 - sender: true 8 - }, 9 - orderBy: { 10 - createdAt: "asc" as const 11 - } 12 - }, 13 - receiver: true, 14 - sender: true 15 - } satisfies Prisma.ChatConversationInclude; 16 - 17 - const messageInclude = { 18 - conversation: { 19 - include: { 20 - receiver: true, 21 - sender: true 22 - } 23 - }, 24 - sender: true 25 - } satisfies Prisma.MessageInclude; 26 - 27 - export const chatService = { 28 - async ensureAccounts(accountIds: string[]) { 29 - const uniqueIds = Array.from(new Set(accountIds)); 30 - await Promise.all( 31 - uniqueIds.map((id) => 32 - prisma.lensAccount.upsert({ 33 - create: { id }, 34 - update: {}, 35 - where: { id } 36 - }) 37 - ) 38 - ); 39 - return uniqueIds; 40 - }, 41 - 42 - async getConversation(conversationId: string) { 43 - return prisma.chatConversation.findUnique({ 44 - include: conversationInclude, 45 - where: { id: conversationId } 46 - }); 47 - }, 48 - 49 - async getOrCreateConversation(senderId: string, receiverId: string) { 50 - if (senderId === receiverId) { 51 - throw new Error("Sender and receiver must be different accounts"); 52 - } 53 - 54 - await this.ensureAccounts([senderId, receiverId]); 55 - 56 - const sortedKey = [senderId, receiverId].sort().join(":"); 57 - 58 - const existing = await prisma.chatConversation.findUnique({ 59 - include: conversationInclude, 60 - where: { key: sortedKey } 61 - }); 62 - 63 - if (existing) { 64 - return existing; 65 - } 66 - 67 - return prisma.chatConversation.create({ 68 - data: { 69 - key: sortedKey, 70 - receiver: { 71 - connect: { id: receiverId } 72 - }, 73 - sender: { 74 - connect: { id: senderId } 75 - } 76 - }, 77 - include: conversationInclude 78 - }); 79 - }, 80 - 81 - async sendMessage(conversationId: string, senderId: string, content: string) { 82 - await this.ensureAccounts([senderId]); 83 - const conversation = await prisma.chatConversation.findUnique({ 84 - select: { 85 - id: true, 86 - receiverId: true, 87 - senderId: true 88 - }, 89 - where: { id: conversationId } 90 - }); 91 - 92 - if (!conversation) { 93 - throw new Error("Conversation not found"); 94 - } 95 - 96 - if ( 97 - conversation.senderId !== senderId && 98 - conversation.receiverId !== senderId 99 - ) { 100 - throw new Error("Sender is not part of the conversation"); 101 - } 102 - 103 - const message = await prisma.message.create({ 104 - data: { 105 - content, 106 - conversationId, 107 - senderId 108 - }, 109 - include: messageInclude 110 - }); 111 - 112 - await prisma.chatConversation.update({ 113 - data: { 114 - updatedAt: new Date() 115 - }, 116 - where: { id: conversationId } 117 - }); 118 - 119 - return message; 120 - } 121 - };
-8
apps/api/src/routes/chat/index.ts
··· 1 - import { Hono } from "hono"; 2 - import conversationsRouter from "./conversations"; 3 - 4 - const app = new Hono(); 5 - 6 - app.route("/conversations", conversationsRouter); 7 - 8 - export default app;
-20
apps/api/src/utils/prisma.ts
··· 1 - import { PrismaClient } from "@prisma/client"; 2 - 3 - const globalForPrisma = globalThis as unknown as { 4 - prisma?: PrismaClient; 5 - }; 6 - 7 - const prisma = 8 - globalForPrisma.prisma ?? 9 - new PrismaClient({ 10 - log: 11 - process.env.NODE_ENV === "development" 12 - ? ["query", "error", "warn"] 13 - : ["error"] 14 - }); 15 - 16 - if (process.env.NODE_ENV !== "production") { 17 - globalForPrisma.prisma = prisma; 18 - } 19 - 20 - export default prisma;
+1 -5
apps/web/src/components/Account/Details.tsx
··· 1 - import { CalendarIcon, MapPinIcon } from "@heroicons/react/24/outline"; 1 + import { MapPinIcon } from "@heroicons/react/24/outline"; 2 2 import { BeakerIcon, CheckBadgeIcon } from "@heroicons/react/24/solid"; 3 3 import { STATIC_IMAGES_URL, TRANSFORMS } from "@hey/data/constants"; 4 4 import getAccount from "@hey/helpers/getAccount"; 5 5 import getAvatar from "@hey/helpers/getAvatar"; 6 6 import type { AccountFragment } from "@hey/indexer"; 7 - import dayjs from "dayjs"; 8 7 import type { ReactNode } from "react"; 9 8 import { useCallback, useState } from "react"; 10 9 import { Link, useNavigate } from "react-router"; ··· 191 190 /> 192 191 )} 193 192 {account.isBeta && <CreatorCoin account={account} />} 194 - <MetaDetails icon={<CalendarIcon className="size-4" />}> 195 - Joined {dayjs(account.createdAt).format("MMM YYYY")} 196 - </MetaDetails> 197 193 </div> 198 194 </div> 199 195 </div>
-1
packages/indexer/documents/fragments/account/Account.graphql
··· 19 19 ...Username 20 20 } 21 21 ...Permissions 22 - createdAt 23 22 }