first commit
This commit is contained in:
25
backend/package.json
Normal file
25
backend/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "0.1.0",
|
||||
"main": "src/index.ts",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bun --hot src/index.ts",
|
||||
"start": "NODE_ENV=production bun src/index.ts",
|
||||
"start:dist": "bun run dist/server",
|
||||
"build": "bun build --compile --minify-whitespace --minify-syntax --target bun --outfile dist/server src/index.ts",
|
||||
"typecheck": "bun --bun tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"elysia": "^1.0.0",
|
||||
"prisma": "^5.0.0",
|
||||
"@prisma/client": "^5.0.0",
|
||||
"better-auth": "^0.7.0",
|
||||
"@elysiajs/cors": "^1.0.0",
|
||||
"@elysiajs/eden": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
}
|
||||
}
|
||||
86
backend/prisma/migrations/20260306034508_init/migration.sql
Normal file
86
backend/prisma/migrations/20260306034508_init/migration.sql
Normal file
@@ -0,0 +1,86 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT,
|
||||
"email" TEXT,
|
||||
"emailVerified" DATETIME,
|
||||
"phone" TEXT,
|
||||
"phoneVerified" DATETIME,
|
||||
"image" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "accounts" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"provider" TEXT NOT NULL,
|
||||
"provider_account_id" TEXT NOT NULL,
|
||||
"refresh_token" TEXT,
|
||||
"access_token" TEXT,
|
||||
"expires_at" INTEGER,
|
||||
"token_type" TEXT,
|
||||
"scope" TEXT,
|
||||
"id_token" TEXT,
|
||||
"session_state" TEXT,
|
||||
CONSTRAINT "accounts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "sessions" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"session_token" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"expires" DATETIME NOT NULL,
|
||||
CONSTRAINT "sessions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "posts" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "posts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "comments" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"content" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"post_id" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "comments_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "comments_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "posts" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "verification_tokens" (
|
||||
"identifier" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"expires" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_phone_key" ON "User"("phone");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "accounts_provider_provider_account_id_key" ON "accounts"("provider", "provider_account_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "sessions_session_token_key" ON "sessions"("session_token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "verification_tokens_token_key" ON "verification_tokens"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "verification_tokens_identifier_token_key" ON "verification_tokens"("identifier", "token");
|
||||
@@ -0,0 +1,30 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "resources" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"url" TEXT NOT NULL,
|
||||
"icon" TEXT,
|
||||
"category" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- RedefineTables
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_posts" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"published" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "posts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_posts" ("content", "createdAt", "id", "title", "updatedAt", "user_id") SELECT "content", "createdAt", "id", "title", "updatedAt", "user_id" FROM "posts";
|
||||
DROP TABLE "posts";
|
||||
ALTER TABLE "new_posts" RENAME TO "posts";
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
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 = "sqlite"
|
||||
105
backend/prisma/schema.prisma
Normal file
105
backend/prisma/schema.prisma
Normal file
@@ -0,0 +1,105 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = "file:./dev.db"
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
name String?
|
||||
email String? @unique
|
||||
emailVerified DateTime?
|
||||
phone String? @unique
|
||||
phoneVerified DateTime?
|
||||
image String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
posts Post[]
|
||||
comments Comment[]
|
||||
}
|
||||
|
||||
model Account {
|
||||
id String @id @default(cuid())
|
||||
userId String @map("user_id")
|
||||
type String
|
||||
provider String
|
||||
providerAccountId String @map("provider_account_id")
|
||||
refresh_token String?
|
||||
access_token String?
|
||||
expires_at Int?
|
||||
token_type String?
|
||||
scope String?
|
||||
id_token String?
|
||||
session_state String?
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([provider, providerAccountId])
|
||||
@@map("accounts")
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @default(cuid())
|
||||
sessionToken String @unique @map("session_token")
|
||||
userId String @map("user_id")
|
||||
expires DateTime
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("sessions")
|
||||
}
|
||||
|
||||
model Post {
|
||||
id String @id @default(cuid())
|
||||
title String
|
||||
content String
|
||||
userId String @map("user_id")
|
||||
published Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
comments Comment[]
|
||||
|
||||
@@map("posts")
|
||||
}
|
||||
|
||||
model Resource {
|
||||
id String @id @default(cuid())
|
||||
title String
|
||||
description String?
|
||||
url String
|
||||
icon String?
|
||||
category String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@map("resources")
|
||||
}
|
||||
|
||||
model Comment {
|
||||
id String @id @default(cuid())
|
||||
content String
|
||||
userId String @map("user_id")
|
||||
postId String @map("post_id")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("comments")
|
||||
}
|
||||
|
||||
model VerificationToken {
|
||||
identifier String
|
||||
token String @unique
|
||||
expires DateTime
|
||||
|
||||
@@unique([identifier, token])
|
||||
@@map("verification_tokens")
|
||||
}
|
||||
20
backend/src/auth.ts
Normal file
20
backend/src/auth.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { betterAuth } from 'better-auth';
|
||||
import { prismaAdapter } from 'better-auth/adapters/prisma';
|
||||
import { prisma } from './prisma';
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: prismaAdapter(prisma, {
|
||||
provider: 'sqlite'
|
||||
}),
|
||||
basePath: '/api',
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
socialProviders: {
|
||||
github: {
|
||||
clientId: process.env.GITHUB_CLIENT_ID || 'dummy',
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET || 'dummy',
|
||||
enabled: !!(process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET),
|
||||
},
|
||||
},
|
||||
});
|
||||
35
backend/src/index.ts
Normal file
35
backend/src/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Elysia } from 'elysia';
|
||||
import { cors } from '@elysiajs/cors';
|
||||
import { auth } from './auth';
|
||||
import { prisma } from './prisma';
|
||||
import { resources } from './resources';
|
||||
import { posts } from './posts';
|
||||
|
||||
const app = new Elysia()
|
||||
.use(
|
||||
cors({
|
||||
origin: 'http://localhost:5173',
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
credentials: true,
|
||||
allowedHeaders: ['Content-Type', 'Authorization']
|
||||
})
|
||||
)
|
||||
.mount('/auth', auth.handler)
|
||||
.get('/health', () => 'OK')
|
||||
.use(resources)
|
||||
.use(posts)
|
||||
.get('/user/:id', async ({ params }) => {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: params.id
|
||||
}
|
||||
});
|
||||
return user;
|
||||
});
|
||||
|
||||
app.listen(3001, () => {
|
||||
console.log('Server running on port 3001');
|
||||
});
|
||||
|
||||
export default app;
|
||||
export type App = typeof app;
|
||||
38
backend/src/posts.ts
Normal file
38
backend/src/posts.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Elysia, t } from 'elysia';
|
||||
import { prisma } from './prisma';
|
||||
|
||||
export const posts = new Elysia({ prefix: '/posts' })
|
||||
.get('/', async () => {
|
||||
return await prisma.post.findMany({
|
||||
include: { user: true },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
})
|
||||
.get('/:id', async ({ params: { id } }) => {
|
||||
return await prisma.post.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
user: true,
|
||||
comments: {
|
||||
include: {
|
||||
user: true
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.post('/', async ({ body }) => {
|
||||
return await prisma.post.create({
|
||||
data: body
|
||||
});
|
||||
}, {
|
||||
body: t.Object({
|
||||
title: t.String(),
|
||||
content: t.String(),
|
||||
userId: t.String(),
|
||||
published: t.Optional(t.Boolean())
|
||||
})
|
||||
});
|
||||
3
backend/src/prisma.ts
Normal file
3
backend/src/prisma.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
export const prisma = new PrismaClient();
|
||||
41
backend/src/resources.ts
Normal file
41
backend/src/resources.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Elysia, t } from 'elysia';
|
||||
import { prisma } from './prisma';
|
||||
|
||||
export const resources = new Elysia({ prefix: '/resources' })
|
||||
.get('/', async () => {
|
||||
return await prisma.resource.findMany({
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
})
|
||||
.post('/', async ({ body }) => {
|
||||
return await prisma.resource.create({
|
||||
data: body
|
||||
});
|
||||
}, {
|
||||
body: t.Object({
|
||||
title: t.String(),
|
||||
description: t.Optional(t.String()),
|
||||
url: t.String(),
|
||||
icon: t.Optional(t.String()),
|
||||
category: t.Optional(t.String())
|
||||
})
|
||||
})
|
||||
.put('/:id', async ({ params: { id }, body }) => {
|
||||
return await prisma.resource.update({
|
||||
where: { id },
|
||||
data: body
|
||||
});
|
||||
}, {
|
||||
body: t.Object({
|
||||
title: t.Optional(t.String()),
|
||||
description: t.Optional(t.String()),
|
||||
url: t.Optional(t.String()),
|
||||
icon: t.Optional(t.String()),
|
||||
category: t.Optional(t.String())
|
||||
})
|
||||
})
|
||||
.delete('/:id', async ({ params: { id } }) => {
|
||||
return await prisma.resource.delete({
|
||||
where: { id }
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user