feat: initialize project with Docker, PostgreSQL, Redis, and Vue.js frontend
- Added docker-compose.yml for PostgreSQL and Redis services with health checks. - Created frontend directory with initial Vue.js setup including package.json, vite.config.ts, and TypeScript configuration. - Implemented main application structure with App.vue and HomePage.vue components. - Added message fetching and posting functionality in HomePage.vue. - Included necessary styles and scripts for Ionic framework integration. - Developed a dev-stack script to manage Docker containers and run backend/frontend servers.
This commit is contained in:
4
backend/.env.example
Normal file
4
backend/.env.example
Normal file
@@ -0,0 +1,4 @@
|
||||
DATABASE_URL="postgresql://USER:PASSWORD@localhost:5432/xip"
|
||||
REDIS_URL="redis://localhost:6379"
|
||||
PORT=3000
|
||||
NODE_ENV=development
|
||||
20
backend/package.json
Normal file
20
backend/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "xip-backend",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "bun --hot run src/index.ts",
|
||||
"start": "bun run src/index.ts",
|
||||
"build": "bun build src/index.ts --outdir dist --target bun"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.22.0",
|
||||
"hono": "^4.6.0",
|
||||
"ioredis": "^5.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"prisma": "^5.22.0",
|
||||
"typescript": "^5.6.0"
|
||||
}
|
||||
}
|
||||
13
backend/prisma/migrations/20260529094703_init/migration.sql
Normal file
13
backend/prisma/migrations/20260529094703_init/migration.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "messages" (
|
||||
"id" TEXT NOT NULL,
|
||||
"content" VARCHAR(267) NOT NULL,
|
||||
"authorIp" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"parentId" TEXT,
|
||||
|
||||
CONSTRAINT "messages_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "messages" ADD CONSTRAINT "messages_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "messages"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
3
backend/prisma/migrations/migration_lock.toml
Normal file
3
backend/prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
24
backend/prisma/schema.prisma
Normal file
24
backend/prisma/schema.prisma
Normal file
@@ -0,0 +1,24 @@
|
||||
// This is your Prisma schema file
|
||||
// Learn more: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Message {
|
||||
id String @id @default(uuid())
|
||||
content String @db.VarChar(267)
|
||||
authorIp String
|
||||
createdAt DateTime @default(now())
|
||||
parentId String?
|
||||
|
||||
parent Message? @relation("ThreadReplies", fields: [parentId], references: [id])
|
||||
replies Message[] @relation("ThreadReplies")
|
||||
|
||||
@@map("messages")
|
||||
}
|
||||
39
backend/prisma/seed.ts
Normal file
39
backend/prisma/seed.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const count = await prisma.message.count();
|
||||
if (count > 0) {
|
||||
console.log("⏭️ Database already seeded, skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
const root1 = await prisma.message.create({
|
||||
data: {
|
||||
content: "Bienvenue sur XIP — le réseau social sans filtre ni compte.",
|
||||
authorIp: "1.2.3.4",
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.message.create({
|
||||
data: {
|
||||
content: "Pas de compte, ton IP c'est toi.",
|
||||
authorIp: "5.6.7.8",
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.message.create({
|
||||
data: {
|
||||
content: "Réponse au premier message !",
|
||||
authorIp: "9.10.11.12",
|
||||
parentId: root1.id,
|
||||
},
|
||||
});
|
||||
|
||||
console.log("✅ Database seeded with 3 messages.");
|
||||
}
|
||||
|
||||
main()
|
||||
.catch(console.error)
|
||||
.finally(() => prisma.$disconnect());
|
||||
24
backend/src/index.ts
Normal file
24
backend/src/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Hono } from "hono";
|
||||
import { cors } from "hono/cors";
|
||||
import { logger } from "hono/logger";
|
||||
import messagesRoute from "./routes/messages";
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
app.use("*", logger());
|
||||
app.use(
|
||||
"*",
|
||||
cors({
|
||||
origin: ["http://localhost:5173"],
|
||||
allowMethods: ["GET", "POST", "OPTIONS"],
|
||||
allowHeaders: ["Content-Type"],
|
||||
})
|
||||
);
|
||||
|
||||
app.get("/health", (c) => c.json({ status: "ok" }));
|
||||
app.route("/api/messages", messagesRoute);
|
||||
|
||||
export default {
|
||||
port: Number(process.env.PORT) || 3000,
|
||||
fetch: app.fetch,
|
||||
};
|
||||
10
backend/src/lib/prisma.ts
Normal file
10
backend/src/lib/prisma.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
|
||||
|
||||
export const prisma =
|
||||
globalForPrisma.prisma ?? new PrismaClient({ log: ["error", "warn"] });
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
globalForPrisma.prisma = prisma;
|
||||
}
|
||||
46
backend/src/routes/messages.ts
Normal file
46
backend/src/routes/messages.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Hono } from "hono";
|
||||
import { prisma } from "../lib/prisma";
|
||||
|
||||
const messages = new Hono();
|
||||
|
||||
// GET /api/messages — top-level threads with replies
|
||||
messages.get("/", async (c) => {
|
||||
const data = await prisma.message.findMany({
|
||||
where: { parentId: null },
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 50,
|
||||
include: {
|
||||
replies: {
|
||||
orderBy: { createdAt: "asc" },
|
||||
},
|
||||
},
|
||||
});
|
||||
return c.json(data);
|
||||
});
|
||||
|
||||
// POST /api/messages — create a message or reply
|
||||
messages.post("/", async (c) => {
|
||||
const ip =
|
||||
c.req.header("x-forwarded-for")?.split(",")[0].trim() ?? "127.0.0.1";
|
||||
|
||||
const body = await c.req.json<{ content: string; parentId?: string }>();
|
||||
|
||||
if (!body.content || body.content.trim().length === 0) {
|
||||
return c.json({ error: "Content is required" }, 400);
|
||||
}
|
||||
if (body.content.length > 267) {
|
||||
return c.json({ error: "Content exceeds 267 characters" }, 400);
|
||||
}
|
||||
|
||||
const message = await prisma.message.create({
|
||||
data: {
|
||||
content: body.content.trim(),
|
||||
authorIp: ip,
|
||||
parentId: body.parentId ?? null,
|
||||
},
|
||||
});
|
||||
|
||||
return c.json(message, 201);
|
||||
});
|
||||
|
||||
export default messages;
|
||||
12
backend/tsconfig.json
Normal file
12
backend/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"types": ["bun-types"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "prisma/**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user