Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@

# Database configuration
DATABASE_URL="postgresql://USER:PASSWORD@localhost:5432/arcana_db?schema=public"

# JWT Configuration
JWT_SECRET="your-super-secret-jwt-key-change-this-in-production"
PORT=3001
141 changes: 141 additions & 0 deletions backend/API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Arcana Backend API Documentation

## Base URL
`http://localhost:3001`

---

## Authentication Endpoints

### Register User
**POST** `/api/users/register`

Request Body:
```json
{
"username": "john_doe",
"email": "john@example.com",
"password": "securepassword123"
}
```

Response (201 Created):
```json
{
"user": {
"id": "cuid...",
"username": "john_doe",
"email": "john@example.com",
"profile": null
},
"token": "jwt_token_here"
}
```

### Login User
**POST** `/api/users/login`

Request Body:
```json
{
"email": "john@example.com",
"password": "securepassword123"
}
```

Response (200 OK):
```json
{
"user": {
"id": "cuid...",
"username": "john_doe",
"email": "john@example.com",
"profile": null
},
"token": "jwt_token_here"
}
```

### Get All Users
**GET** `/api/users`

Response (200 OK):
```json
{
"users": [
{
"id": "cuid...",
"username": "john_doe",
"email": "john@example.com",
"profile": null,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
}
]
}
```

### Get User By ID
**GET** `/api/users/:id`

---

## Transaction Endpoints

### Create Transaction
**POST** `/api/transactions`

Request Body:
```json
{
"userId": "cuid...",
"type": "deposit",
"amount": 100.50,
"currency": "ETH",
"status": "completed",
"hash": "0x..."
}
```

Transaction Types: `deposit`, `withdraw`, `transfer`, `swap`
Transaction Status: `pending`, `completed`, `failed`, `cancelled`

Response (201 Created):
```json
{
"transaction": {
"id": "cuid...",
"userId": "cuid...",
"type": "deposit",
"amount": 100.5,
"currency": "ETH",
"status": "completed",
"hash": "0x...",
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
}
}
```

### Get All Transactions
**GET** `/api/transactions`

### Get Transaction By ID
**GET** `/api/transactions/:id`

### Get Transactions By User ID
**GET** `/api/transactions/user/:userId`

---

## Health Check
**GET** `/health`

Response:
```json
{
"status": "ok",
"database": "connected",
"uptime": 123.45
}
```
1 change: 1 addition & 0 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ model User {
id String @id @default(cuid())
username String @unique
email String @unique
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
profile Profile?
Expand Down
57 changes: 57 additions & 0 deletions backend/src/controllers/transaction.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Request, Response } from 'express';
import prisma from '../lib/prisma';
import { CreateTransactionInput } from '../validations/transaction.validations';

export const createTransaction = async (
req: Request<{}, {}, CreateTransactionInput>,
res: Response
) => {
const transaction = await prisma.transaction.create({
data: req.body,
include: {
user: true,
},
});

res.status(201).json({ transaction });
};

export const getTransactions = async (_req: Request, res: Response) => {
const transactions = await prisma.transaction.findMany({
include: {
user: true,
},
});

res.json({ transactions });
};

export const getTransactionById = async (req: Request, res: Response) => {
const { id } = req.params;

const transaction = await prisma.transaction.findUnique({
where: { id },
include: {
user: true,
},
});

if (!transaction) {
return res.status(404).json({ error: 'Transaction not found' });
}

res.json({ transaction });
};

export const getTransactionsByUserId = async (req: Request, res: Response) => {
const { userId } = req.params;

const transactions = await prisma.transaction.findMany({
where: { userId },
include: {
user: true,
},
});

res.json({ transactions });
};
106 changes: 106 additions & 0 deletions backend/src/controllers/user.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Request, Response } from 'express';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import prisma from '../lib/prisma';
import { RegisterUserInput, LoginUserInput } from '../validations/user.validations';

const JWT_SECRET = process.env.JWT_SECRET || 'arcana-secret-key-change-in-production';

export const registerUser = async (req: Request<{}, {}, RegisterUserInput>, res: Response) => {
const { username, email, password } = req.body;

const existingUser = await prisma.user.findFirst({
where: {
OR: [{ email }, { username }],
},
});

if (existingUser) {
return res.status(409).json({ error: 'User with email or username already exists' });
}

const hashedPassword = await bcrypt.hash(password, 12);

const user = await prisma.user.create({
data: {
username,
email,
password: hashedPassword,
},
include: {
profile: true,
},
});

const token = jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: '7d' });

res.status(201).json({
user: {
id: user.id,
username: user.username,
email: user.email,
profile: user.profile,
},
token,
});
};

export const loginUser = async (req: Request<{}, {}, LoginUserInput>, res: Response) => {
const { email, password } = req.body;

const user = await prisma.user.findUnique({
where: { email },
include: {
profile: true,
},
});

if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}

const passwordMatch = await bcrypt.compare(password, user.password);

if (!passwordMatch) {
return res.status(401).json({ error: 'Invalid credentials' });
}

const token = jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: '7d' });

res.json({
user: {
id: user.id,
username: user.username,
email: user.email,
profile: user.profile,
},
token,
});
};

export const getUsers = async (_req: Request, res: Response) => {
const users = await prisma.user.findMany({
include: {
profile: true,
},
});

res.json({ users });
};

export const getUserById = async (req: Request, res: Response) => {
const { id } = req.params;

const user = await prisma.user.findUnique({
where: { id },
include: {
profile: true,
},
});

if (!user) {
return res.status(404).json({ error: 'User not found' });
}

res.json({ user });
};
52 changes: 11 additions & 41 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import 'dotenv/config'
import 'dotenv/config';
import express from 'express';
import morgan from 'morgan';
import cors from 'cors';
import prisma from './lib/prisma';
import userRoutes from './routes/user.routes';
import transactionRoutes from './routes/transaction.routes';
import { errorHandler } from './middleware/error.middleware';

const app = express();
const PORT = 3001;
const PORT = process.env.PORT || 3001;

app.use(cors());
app.use(morgan('dev'));
app.use(express.json());

app.get('/', (_req, res) => {
res.send('Arcana Backend - Database Integrated');
res.send('Arcana Backend - API v1');
});

app.get('/health', async (_req, res) => {
Expand All @@ -22,45 +27,10 @@ app.get('/health', async (_req, res) => {
}
});

app.get('/users', async (_req, res) => {
try {
const users = await prisma.user.findMany({ include: { profile: true } });
res.json({ users });
} catch (error) {
res.status(500).json({ error: String(error) });
}
});

app.post('/users', async (req, res) => {
try {
const { username, email } = req.body;
const user = await prisma.user.create({ data: { username, email } });
res.json({ user });
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
app.use('/api/users', userRoutes);
app.use('/api/transactions', transactionRoutes);

app.get('/transactions', async (_req, res) => {
try {
const transactions = await prisma.transaction.findMany();
res.json({ transactions });
} catch (error) {
res.status(500).json({ error: String(error) });
}
});

app.post('/transactions', async (req, res) => {
try {
const { userId, type, amount, currency, status } = req.body;
const transaction = await prisma.transaction.create({
data: { userId, type, amount, currency, status }
});
res.json({ transaction });
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
app.use(errorHandler);

app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
Expand Down
Loading