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:
Juan Sebastián Montoya 2025-03-04 01:08:52 -05:00
parent 8f3aa2fc26
commit 16731409df
81 changed files with 13585 additions and 1163 deletions

View 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,
};

View 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 },
});
},
},
};

View 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 },
});
},
},
};

View 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 },
});
},
},
};