refactor: migrate from Next.js to SolidJS and GraphQL
- Converted web application from Next.js to SolidJS with Vite - Replaced React components with SolidJS components - Implemented GraphQL client using URQL - Added authentication, room, and chat components - Updated project structure and configuration files - Removed unnecessary Next.js and docs-related files - Added Docker support for web and API applications
This commit is contained in:
parent
8f3aa2fc26
commit
16731409df
81 changed files with 13585 additions and 1163 deletions
4
apps/api/.dockerignore
Normal file
4
apps/api/.dockerignore
Normal file
|
@ -0,0 +1,4 @@
|
|||
.env
|
||||
node_modules
|
||||
dist
|
||||
.turbo
|
3
apps/api/.gitignore
vendored
Normal file
3
apps/api/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
# Keep environment variables out of version control
|
||||
.env
|
17
apps/api/Dockerfile
Normal file
17
apps/api/Dockerfile
Normal file
|
@ -0,0 +1,17 @@
|
|||
FROM node:22-alpine AS base
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
# Install dependencies
|
||||
RUN npm install
|
||||
|
||||
# Generate Prisma client
|
||||
RUN npm run prisma:generate
|
||||
|
||||
# Build the project
|
||||
RUN npm run api:build
|
||||
|
||||
# Start the server
|
||||
ENTRYPOINT [ "npm", "run", "api:start" ]
|
91
apps/api/README.md
Normal file
91
apps/api/README.md
Normal file
|
@ -0,0 +1,91 @@
|
|||
# Unreal Chat API
|
||||
|
||||
The backend API for the Unreal Chat application, built with Apollo Server, GraphQL, and Prisma.
|
||||
|
||||
## Features
|
||||
|
||||
- GraphQL API with Apollo Server
|
||||
- Real-time subscriptions for messages and rooms
|
||||
- Prisma ORM with MariaDB
|
||||
- User authentication
|
||||
- Chat rooms and messaging
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js (v18 or later)
|
||||
- npm (v10 or later)
|
||||
- MariaDB or MySQL
|
||||
|
||||
### Installation
|
||||
|
||||
1. Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Set up the database:
|
||||
|
||||
Make sure you have MariaDB running and update the connection string in `prisma/.env` if needed.
|
||||
|
||||
3. Run Prisma migrations:
|
||||
|
||||
```bash
|
||||
npm run prisma:migrate
|
||||
```
|
||||
|
||||
4. Start the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The API will be available at http://localhost:4000/graphql.
|
||||
|
||||
## Available Scripts
|
||||
|
||||
- `npm run dev` - Start the development server
|
||||
- `npm run build` - Build the application
|
||||
- `npm run start` - Start the production server
|
||||
- `npm run prisma:generate` - Generate Prisma client
|
||||
- `npm run prisma:migrate` - Run Prisma migrations
|
||||
- `npm run prisma:studio` - Open Prisma Studio
|
||||
|
||||
## API Documentation
|
||||
|
||||
### Authentication
|
||||
|
||||
For development purposes, authentication is simplified. In a production environment, you would use JWT tokens.
|
||||
|
||||
### GraphQL Schema
|
||||
|
||||
The GraphQL schema includes the following main types:
|
||||
|
||||
- `User`: Represents a user in the system
|
||||
- `Room`: Represents a chat room
|
||||
- `Message`: Represents a message in a chat room
|
||||
|
||||
### Queries
|
||||
|
||||
- `me`: Get the current user
|
||||
- `users`: Get all users
|
||||
- `user(id: ID!)`: Get a user by ID
|
||||
- `rooms`: Get all public rooms
|
||||
- `room(id: ID!)`: Get a room by ID
|
||||
- `messages(roomId: ID!)`: Get messages in a room
|
||||
|
||||
### Mutations
|
||||
|
||||
- `register(email: String!, username: String!, password: String!)`: Register a new user
|
||||
- `login(email: String!, password: String!)`: Login a user
|
||||
- `createRoom(name: String!, description: String, isPrivate: Boolean)`: Create a new room
|
||||
- `joinRoom(roomId: ID!)`: Join a room
|
||||
- `leaveRoom(roomId: ID!)`: Leave a room
|
||||
- `sendMessage(content: String!, roomId: ID!)`: Send a message to a room
|
||||
|
||||
### Subscriptions
|
||||
|
||||
- `messageAdded(roomId: ID!)`: Subscribe to new messages in a room
|
||||
- `roomAdded`: Subscribe to new rooms
|
44
apps/api/package.json
Normal file
44
apps/api/package.json
Normal file
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "api",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon --exec ts-node src/index.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate": "prisma migrate dev",
|
||||
"prisma:studio": "prisma studio",
|
||||
"prisma:init": "prisma migrate dev --name init",
|
||||
"check-types": "tsc --noEmit"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"@apollo/server": "^4.10.0",
|
||||
"@graphql-tools/schema": "^10.0.2",
|
||||
"@prisma/client": "^6.4.1",
|
||||
"@types/ws": "^8.5.14",
|
||||
"apollo-server": "^3.13.0",
|
||||
"apollo-server-express": "^3.13.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.18.2",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-subscriptions": "^2.0.0",
|
||||
"graphql-ws": "^5.14.0",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"ws": "^8.18.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.11.20",
|
||||
"nodemon": "^3.1.0",
|
||||
"prisma": "^6.4.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
50
apps/api/prisma/schema.prisma
Normal file
50
apps/api/prisma/schema.prisma
Normal file
|
@ -0,0 +1,50 @@
|
|||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
|
||||
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "mysql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
email String @unique
|
||||
username String @unique
|
||||
password String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
messages Message[]
|
||||
rooms Room[] @relation("RoomMembers")
|
||||
ownedRooms Room[] @relation("RoomOwner")
|
||||
}
|
||||
|
||||
model Room {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
description String?
|
||||
isPrivate Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
messages Message[]
|
||||
members User[] @relation("RoomMembers")
|
||||
ownerId String
|
||||
owner User @relation("RoomOwner", fields: [ownerId], references: [id])
|
||||
}
|
||||
|
||||
model Message {
|
||||
id String @id @default(uuid())
|
||||
content String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
roomId String
|
||||
room Room @relation(fields: [roomId], references: [id])
|
||||
}
|
93
apps/api/src/index.ts
Normal file
93
apps/api/src/index.ts
Normal file
|
@ -0,0 +1,93 @@
|
|||
import { ApolloServer } from '@apollo/server';
|
||||
import { expressMiddleware } from '@apollo/server/express4';
|
||||
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
|
||||
import { makeExecutableSchema } from '@graphql-tools/schema';
|
||||
import { createServer } from 'http';
|
||||
import express from 'express';
|
||||
import { WebSocketServer } from 'ws';
|
||||
import { useServer } from 'graphql-ws/lib/use/ws';
|
||||
import cors from 'cors';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { typeDefs } from './schema/typeDefs';
|
||||
import { resolvers } from './resolvers';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
// Create Prisma client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function startServer() {
|
||||
// Create Express app and HTTP server
|
||||
const app = express();
|
||||
const httpServer = createServer(app);
|
||||
|
||||
// Create WebSocket server
|
||||
const wsServer = new WebSocketServer({
|
||||
server: httpServer,
|
||||
path: '/graphql',
|
||||
});
|
||||
|
||||
// Create schema
|
||||
const schema = makeExecutableSchema({ typeDefs, resolvers });
|
||||
|
||||
// Set up WebSocket server
|
||||
const serverCleanup = useServer({ schema }, wsServer);
|
||||
|
||||
// Create Apollo Server
|
||||
const server = new ApolloServer({
|
||||
schema,
|
||||
plugins: [
|
||||
// Proper shutdown for the HTTP server
|
||||
ApolloServerPluginDrainHttpServer({ httpServer }),
|
||||
// Proper shutdown for the WebSocket server
|
||||
{
|
||||
async serverWillStart() {
|
||||
return {
|
||||
async drainServer() {
|
||||
await serverCleanup.dispose();
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Start Apollo Server
|
||||
await server.start();
|
||||
|
||||
// Apply middleware
|
||||
app.use(
|
||||
'/graphql',
|
||||
cors<cors.CorsRequest>(),
|
||||
express.json(),
|
||||
expressMiddleware(server, {
|
||||
context: async ({ req }) => {
|
||||
// In a real application, you would extract the user ID from a JWT token
|
||||
// const token = req.headers.authorization || '';
|
||||
// const userId = getUserIdFromToken(token);
|
||||
|
||||
// For demo purposes, we'll use a dummy user ID
|
||||
const userId = (req.headers['user-id'] as string) || null;
|
||||
|
||||
return {
|
||||
prisma,
|
||||
userId,
|
||||
};
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Start the server
|
||||
const PORT = process.env.PORT || 4000;
|
||||
httpServer.listen(PORT, () => {
|
||||
console.log(`🚀 Server ready at http://localhost:${PORT}/graphql`);
|
||||
console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}/graphql`);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle errors
|
||||
startServer().catch((err) => {
|
||||
console.error('Error starting server:', err);
|
||||
});
|
23
apps/api/src/resolvers/index.ts
Normal file
23
apps/api/src/resolvers/index.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { userResolvers } from './user';
|
||||
import { roomResolvers } from './room';
|
||||
import { messageResolvers } from './message';
|
||||
|
||||
export const resolvers = {
|
||||
Query: {
|
||||
...userResolvers.Query,
|
||||
...roomResolvers.Query,
|
||||
...messageResolvers.Query,
|
||||
},
|
||||
Mutation: {
|
||||
...userResolvers.Mutation,
|
||||
...roomResolvers.Mutation,
|
||||
...messageResolvers.Mutation,
|
||||
},
|
||||
Subscription: {
|
||||
...messageResolvers.Subscription,
|
||||
...roomResolvers.Subscription,
|
||||
},
|
||||
User: userResolvers.User,
|
||||
Room: roomResolvers.Room,
|
||||
Message: messageResolvers.Message,
|
||||
};
|
112
apps/api/src/resolvers/message.ts
Normal file
112
apps/api/src/resolvers/message.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
import { AuthenticationError, ForbiddenError } from 'apollo-server-express';
|
||||
import { PubSub, withFilter } from 'graphql-subscriptions';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
const pubsub = new PubSub();
|
||||
|
||||
export const MESSAGE_ADDED = 'MESSAGE_ADDED';
|
||||
|
||||
export const messageResolvers = {
|
||||
Query: {
|
||||
messages: async (_: any, { roomId }: { roomId: string }, context: any) => {
|
||||
if (!context.userId) {
|
||||
throw new AuthenticationError('You must be logged in to view messages');
|
||||
}
|
||||
|
||||
// Check if user is a member of the room
|
||||
const room = await prisma.room.findUnique({
|
||||
where: { id: roomId },
|
||||
include: { members: true },
|
||||
});
|
||||
|
||||
if (!room) {
|
||||
throw new ForbiddenError('Room not found');
|
||||
}
|
||||
|
||||
const isMember = room.members.some(
|
||||
(member: { id: string }) => member.id === context.userId
|
||||
);
|
||||
if (!isMember) {
|
||||
throw new ForbiddenError('You are not a member of this room');
|
||||
}
|
||||
|
||||
return prisma.message.findMany({
|
||||
where: { roomId },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
});
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
sendMessage: async (
|
||||
_: any,
|
||||
{ content, roomId }: { content: string; roomId: string },
|
||||
context: any
|
||||
) => {
|
||||
if (!context.userId) {
|
||||
throw new AuthenticationError(
|
||||
'You must be logged in to send a message'
|
||||
);
|
||||
}
|
||||
|
||||
// Check if user is a member of the room
|
||||
const room = await prisma.room.findUnique({
|
||||
where: { id: roomId },
|
||||
include: { members: true },
|
||||
});
|
||||
|
||||
if (!room) {
|
||||
throw new ForbiddenError('Room not found');
|
||||
}
|
||||
|
||||
const isMember = room.members.some(
|
||||
(member: { id: string }) => member.id === context.userId
|
||||
);
|
||||
if (!isMember) {
|
||||
throw new ForbiddenError('You are not a member of this room');
|
||||
}
|
||||
|
||||
const message = await prisma.message.create({
|
||||
data: {
|
||||
content,
|
||||
user: {
|
||||
connect: { id: context.userId },
|
||||
},
|
||||
room: {
|
||||
connect: { id: roomId },
|
||||
},
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
room: true,
|
||||
},
|
||||
});
|
||||
|
||||
pubsub.publish(MESSAGE_ADDED, { messageAdded: message, roomId });
|
||||
|
||||
return message;
|
||||
},
|
||||
},
|
||||
Subscription: {
|
||||
messageAdded: {
|
||||
subscribe: withFilter(
|
||||
() => pubsub.asyncIterator([MESSAGE_ADDED]),
|
||||
(payload, variables) => {
|
||||
return payload.roomId === variables.roomId;
|
||||
}
|
||||
),
|
||||
},
|
||||
},
|
||||
Message: {
|
||||
user: async (parent: any) => {
|
||||
return prisma.user.findUnique({
|
||||
where: { id: parent.userId },
|
||||
});
|
||||
},
|
||||
room: async (parent: any) => {
|
||||
return prisma.room.findUnique({
|
||||
where: { id: parent.roomId },
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
161
apps/api/src/resolvers/room.ts
Normal file
161
apps/api/src/resolvers/room.ts
Normal file
|
@ -0,0 +1,161 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
import { AuthenticationError, ForbiddenError } from 'apollo-server-express';
|
||||
import { PubSub } from 'graphql-subscriptions';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
const pubsub = new PubSub();
|
||||
|
||||
export const ROOM_ADDED = 'ROOM_ADDED';
|
||||
|
||||
export const roomResolvers = {
|
||||
Query: {
|
||||
rooms: async () => {
|
||||
return prisma.room.findMany({
|
||||
where: { isPrivate: false },
|
||||
});
|
||||
},
|
||||
room: async (_: any, { id }: { id: string }) => {
|
||||
return prisma.room.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
createRoom: async (
|
||||
_: any,
|
||||
{
|
||||
name,
|
||||
description,
|
||||
isPrivate = false,
|
||||
}: { name: string; description?: string; isPrivate?: boolean },
|
||||
context: any
|
||||
) => {
|
||||
if (!context.userId) {
|
||||
throw new AuthenticationError('You must be logged in to create a room');
|
||||
}
|
||||
|
||||
const room = await prisma.room.create({
|
||||
data: {
|
||||
name,
|
||||
description,
|
||||
isPrivate,
|
||||
owner: {
|
||||
connect: { id: context.userId },
|
||||
},
|
||||
members: {
|
||||
connect: { id: context.userId },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
pubsub.publish(ROOM_ADDED, { roomAdded: room });
|
||||
|
||||
return room;
|
||||
},
|
||||
joinRoom: async (_: any, { roomId }: { roomId: string }, context: any) => {
|
||||
if (!context.userId) {
|
||||
throw new AuthenticationError('You must be logged in to join a room');
|
||||
}
|
||||
|
||||
const room = await prisma.room.findUnique({
|
||||
where: { id: roomId },
|
||||
include: { members: true },
|
||||
});
|
||||
|
||||
if (!room) {
|
||||
throw new ForbiddenError('Room not found');
|
||||
}
|
||||
|
||||
if (room.isPrivate) {
|
||||
// In a real application, you would check if the user has been invited
|
||||
throw new ForbiddenError(
|
||||
'You cannot join a private room without an invitation'
|
||||
);
|
||||
}
|
||||
|
||||
// Check if user is already a member
|
||||
const isMember = room.members.some(
|
||||
(member: { id: string }) => member.id === context.userId
|
||||
);
|
||||
if (isMember) {
|
||||
return room;
|
||||
}
|
||||
|
||||
return prisma.room.update({
|
||||
where: { id: roomId },
|
||||
data: {
|
||||
members: {
|
||||
connect: { id: context.userId },
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
leaveRoom: async (_: any, { roomId }: { roomId: string }, context: any) => {
|
||||
if (!context.userId) {
|
||||
throw new AuthenticationError('You must be logged in to leave a room');
|
||||
}
|
||||
|
||||
const room = await prisma.room.findUnique({
|
||||
where: { id: roomId },
|
||||
include: { members: true },
|
||||
});
|
||||
|
||||
if (!room) {
|
||||
throw new ForbiddenError('Room not found');
|
||||
}
|
||||
|
||||
// Check if user is a member
|
||||
const isMember = room.members.some(
|
||||
(member: { id: string }) => member.id === context.userId
|
||||
);
|
||||
if (!isMember) {
|
||||
throw new ForbiddenError('You are not a member of this room');
|
||||
}
|
||||
|
||||
// If user is the owner, they cannot leave
|
||||
if (room.ownerId === context.userId) {
|
||||
throw new ForbiddenError('You cannot leave a room you own');
|
||||
}
|
||||
|
||||
await prisma.room.update({
|
||||
where: { id: roomId },
|
||||
data: {
|
||||
members: {
|
||||
disconnect: { id: context.userId },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
Subscription: {
|
||||
roomAdded: {
|
||||
subscribe: () => pubsub.asyncIterator([ROOM_ADDED]),
|
||||
},
|
||||
},
|
||||
Room: {
|
||||
messages: async (parent: any) => {
|
||||
return prisma.message.findMany({
|
||||
where: { roomId: parent.id },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
});
|
||||
},
|
||||
members: async (parent: any) => {
|
||||
return prisma.user.findMany({
|
||||
where: {
|
||||
rooms: {
|
||||
some: {
|
||||
id: parent.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
owner: async (parent: any) => {
|
||||
return prisma.user.findUnique({
|
||||
where: { id: parent.ownerId },
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
121
apps/api/src/resolvers/user.ts
Normal file
121
apps/api/src/resolvers/user.ts
Normal file
|
@ -0,0 +1,121 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
import { AuthenticationError, UserInputError } from 'apollo-server-express';
|
||||
// In a real application, you would use bcrypt for password hashing
|
||||
// import bcrypt from 'bcryptjs';
|
||||
// import jwt from 'jsonwebtoken';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export const userResolvers = {
|
||||
Query: {
|
||||
me: async (_: any, __: any, context: any) => {
|
||||
// In a real application, you would get the user from the context
|
||||
// which would be set by an authentication middleware
|
||||
if (!context.userId) {
|
||||
return null;
|
||||
}
|
||||
return prisma.user.findUnique({
|
||||
where: { id: context.userId },
|
||||
});
|
||||
},
|
||||
users: async () => {
|
||||
return prisma.user.findMany();
|
||||
},
|
||||
user: async (_: any, { id }: { id: string }) => {
|
||||
return prisma.user.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
register: async (
|
||||
_: any,
|
||||
{
|
||||
email,
|
||||
username,
|
||||
password,
|
||||
}: { email: string; username: string; password: string }
|
||||
) => {
|
||||
// Check if user already exists
|
||||
const existingUser = await prisma.user.findFirst({
|
||||
where: {
|
||||
OR: [{ email }, { username }],
|
||||
},
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
throw new UserInputError('User already exists');
|
||||
}
|
||||
|
||||
// In a real application, you would hash the password
|
||||
// const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email,
|
||||
username,
|
||||
password, // In a real app: hashedPassword
|
||||
},
|
||||
});
|
||||
|
||||
// In a real application, you would generate a JWT token
|
||||
// const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET);
|
||||
|
||||
return {
|
||||
token: 'dummy-token', // In a real app: token
|
||||
user,
|
||||
};
|
||||
},
|
||||
login: async (
|
||||
_: any,
|
||||
{ email, password }: { email: string; password: string }
|
||||
) => {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new AuthenticationError('Invalid credentials');
|
||||
}
|
||||
|
||||
// In a real application, you would verify the password
|
||||
// const valid = await bcrypt.compare(password, user.password);
|
||||
const valid = password === user.password; // This is just for demo purposes
|
||||
|
||||
if (!valid) {
|
||||
throw new AuthenticationError('Invalid credentials');
|
||||
}
|
||||
|
||||
// In a real application, you would generate a JWT token
|
||||
// const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET);
|
||||
|
||||
return {
|
||||
token: 'dummy-token', // In a real app: token
|
||||
user,
|
||||
};
|
||||
},
|
||||
},
|
||||
User: {
|
||||
messages: async (parent: any) => {
|
||||
return prisma.message.findMany({
|
||||
where: { userId: parent.id },
|
||||
});
|
||||
},
|
||||
rooms: async (parent: any) => {
|
||||
return prisma.room.findMany({
|
||||
where: {
|
||||
members: {
|
||||
some: {
|
||||
id: parent.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
ownedRooms: async (parent: any) => {
|
||||
return prisma.room.findMany({
|
||||
where: { ownerId: parent.id },
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
63
apps/api/src/schema/typeDefs.ts
Normal file
63
apps/api/src/schema/typeDefs.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { gql } from 'apollo-server-express';
|
||||
|
||||
export const typeDefs = gql`
|
||||
type User {
|
||||
id: ID!
|
||||
email: String!
|
||||
username: String!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
messages: [Message!]
|
||||
rooms: [Room!]
|
||||
ownedRooms: [Room!]
|
||||
}
|
||||
|
||||
type Room {
|
||||
id: ID!
|
||||
name: String!
|
||||
description: String
|
||||
isPrivate: Boolean!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
messages: [Message!]
|
||||
members: [User!]
|
||||
owner: User!
|
||||
}
|
||||
|
||||
type Message {
|
||||
id: ID!
|
||||
content: String!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
user: User!
|
||||
room: Room!
|
||||
}
|
||||
|
||||
type AuthPayload {
|
||||
token: String!
|
||||
user: User!
|
||||
}
|
||||
|
||||
type Query {
|
||||
me: User
|
||||
users: [User!]!
|
||||
user(id: ID!): User
|
||||
rooms: [Room!]!
|
||||
room(id: ID!): Room
|
||||
messages(roomId: ID!): [Message!]!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
register(email: String!, username: String!, password: String!): AuthPayload!
|
||||
login(email: String!, password: String!): AuthPayload!
|
||||
createRoom(name: String!, description: String, isPrivate: Boolean): Room!
|
||||
joinRoom(roomId: ID!): Room!
|
||||
leaveRoom(roomId: ID!): Boolean!
|
||||
sendMessage(content: String!, roomId: ID!): Message!
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
messageAdded(roomId: ID!): Message!
|
||||
roomAdded: Room!
|
||||
}
|
||||
`;
|
16
apps/api/tsconfig.json
Normal file
16
apps/api/tsconfig.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2018",
|
||||
"module": "commonjs",
|
||||
"lib": ["es2018", "esnext.asynciterable"],
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue