refactor: migrate project to Vue 3 with TypeScript and Vite
- Updated index.html to use Vite and Vue 3. - Changed package.json to reflect new dependencies and build scripts. - Removed old favicon.svg and added new vite.svg. - Deleted unused PWA assets configuration. - Created new App.vue component and HelloWorld.vue component. - Added Vue logo and Vite logo to App.vue. - Removed old JavaScript files and replaced with TypeScript files. - Updated styles in style.css for better layout. - Added TypeScript configuration files for better type checking. - Migrated Vite configuration from JavaScript to TypeScript.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
adminKey: process.env.ADMIN_KEY || 'admin-secret-key-change-this-in-production'
|
||||
}
|
||||
+161
-49
@@ -1,15 +1,22 @@
|
||||
import type { Context } from 'koa';
|
||||
import bcrypt from 'bcrypt';
|
||||
import jwt, { type Secret } from 'jsonwebtoken';
|
||||
import jwt, { type SignOptions } from 'jsonwebtoken';
|
||||
import type { Context } from 'koa';
|
||||
import appConfig from '../config/app';
|
||||
import dbConfig from '../config/database';
|
||||
import jwtConfig from '../config/jwt';
|
||||
import type { User } from '../models';
|
||||
import DatabaseService from '../services/database';
|
||||
|
||||
// Mock user database (in a real app, use a proper database)
|
||||
const users = new Map();
|
||||
const db = DatabaseService.getInstance();
|
||||
|
||||
export default {
|
||||
// User registration
|
||||
async register(ctx: Context) {
|
||||
const { username, password } = ctx.request.body as { username: string, password: string };
|
||||
const { username, password, email } = ctx.request.body as {
|
||||
username: string,
|
||||
password: string,
|
||||
email?: string
|
||||
};
|
||||
|
||||
// Validate input
|
||||
if (!username || !password) {
|
||||
@@ -18,28 +25,117 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user already exists
|
||||
if (users.has(username)) {
|
||||
ctx.status = 409;
|
||||
ctx.body = { success: false, message: 'Username already exists' };
|
||||
try {
|
||||
const usersCollection = db.getCollection<User>(dbConfig.collections.users);
|
||||
|
||||
// Check if user already exists
|
||||
const existingUser = await usersCollection.findOne({ username });
|
||||
if (existingUser) {
|
||||
ctx.status = 409;
|
||||
ctx.body = { success: false, message: 'Username already exists' };
|
||||
return;
|
||||
}
|
||||
|
||||
// Hash password before saving
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
const hashedPassword = await bcrypt.hash(password, salt);
|
||||
|
||||
// Create new user
|
||||
const now = new Date();
|
||||
const newUser: User = {
|
||||
username,
|
||||
password: hashedPassword,
|
||||
email,
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
};
|
||||
|
||||
// Save user to database
|
||||
const result = await usersCollection.insertOne(newUser);
|
||||
|
||||
ctx.status = 201;
|
||||
ctx.body = {
|
||||
success: true,
|
||||
message: 'User registered successfully',
|
||||
userId: result.insertedId
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.status = 500;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Error registering user',
|
||||
error: (error as Error).message
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Admin registration
|
||||
async registerAdmin(ctx: Context) {
|
||||
const { username, password, email, adminKey } = ctx.request.body as {
|
||||
username: string,
|
||||
password: string,
|
||||
email?: string,
|
||||
adminKey: string
|
||||
};
|
||||
|
||||
// Validate input
|
||||
if (!username || !password) {
|
||||
ctx.status = 400;
|
||||
ctx.body = { success: false, message: 'Username and password are required' };
|
||||
return;
|
||||
}
|
||||
|
||||
// Hash password before saving
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
const hashedPassword = await bcrypt.hash(password, salt);
|
||||
// Validate admin key
|
||||
const validAdminKey = appConfig.adminKey;
|
||||
if (adminKey !== validAdminKey) {
|
||||
ctx.status = 403;
|
||||
ctx.body = { success: false, message: 'Invalid admin key' };
|
||||
return;
|
||||
}
|
||||
|
||||
// Save user to database
|
||||
users.set(username, {
|
||||
username,
|
||||
password: hashedPassword
|
||||
});
|
||||
try {
|
||||
const usersCollection = db.getCollection<User>(dbConfig.collections.users);
|
||||
|
||||
ctx.status = 201;
|
||||
ctx.body = {
|
||||
success: true,
|
||||
message: 'User registered successfully'
|
||||
};
|
||||
// Check if user already exists
|
||||
const existingUser = await usersCollection.findOne({ username });
|
||||
if (existingUser) {
|
||||
ctx.status = 409;
|
||||
ctx.body = { success: false, message: 'Username already exists' };
|
||||
return;
|
||||
}
|
||||
|
||||
// Hash password before saving
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
const hashedPassword = await bcrypt.hash(password, salt);
|
||||
|
||||
// Create new admin user
|
||||
const now = new Date();
|
||||
const newUser: User = {
|
||||
username,
|
||||
password: hashedPassword,
|
||||
email,
|
||||
role: 'admin', // Add admin role
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
};
|
||||
|
||||
// Save user to database
|
||||
const result = await usersCollection.insertOne(newUser);
|
||||
|
||||
ctx.status = 201;
|
||||
ctx.body = {
|
||||
success: true,
|
||||
message: 'Admin registered successfully',
|
||||
userId: result.insertedId
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.status = 500;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Error registering admin',
|
||||
error: (error as Error).message
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// User login
|
||||
@@ -53,33 +149,49 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user exists
|
||||
const user = users.get(username);
|
||||
if (!user) {
|
||||
ctx.status = 401;
|
||||
ctx.body = { success: false, message: 'Invalid credentials' };
|
||||
return;
|
||||
try {
|
||||
const usersCollection = db.getCollection<User>(dbConfig.collections.users);
|
||||
|
||||
// Check if user exists
|
||||
const user = await usersCollection.findOne({ username });
|
||||
if (!user) {
|
||||
ctx.status = 401;
|
||||
ctx.body = { success: false, message: 'Invalid credentials' };
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify password
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password);
|
||||
if (!isPasswordValid) {
|
||||
ctx.status = 401;
|
||||
ctx.body = { success: false, message: 'Invalid credentials' };
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate JWT token
|
||||
const token = jwt.sign(
|
||||
{
|
||||
id: user._id?.toString(),
|
||||
username: user.username
|
||||
},
|
||||
jwtConfig.secret,
|
||||
{ expiresIn: jwtConfig.expiresIn } as SignOptions
|
||||
);
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
message: 'Login successful',
|
||||
userId: user._id,
|
||||
username: user.username,
|
||||
token
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.status = 500;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Error during login',
|
||||
error: (error as Error).message
|
||||
};
|
||||
}
|
||||
|
||||
// Verify password
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password);
|
||||
if (!isPasswordValid) {
|
||||
ctx.status = 401;
|
||||
ctx.body = { success: false, message: 'Invalid credentials' };
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate JWT token
|
||||
const token = jwt.sign(
|
||||
{ username: user.username },
|
||||
jwtConfig.secret as Secret,
|
||||
{ expiresIn: jwtConfig.expiresIn }
|
||||
);
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
message: 'Login successful',
|
||||
token
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,166 +1,166 @@
|
||||
import type { Context } from 'koa';
|
||||
import DatabaseService from '../services/database';
|
||||
import { ObjectId } from 'mongodb';
|
||||
import dbConfig from '../config/database';
|
||||
import type { Category } from '../models';
|
||||
import { ObjectId } from 'mongodb';
|
||||
import DatabaseService from '../services/database';
|
||||
|
||||
const db = DatabaseService.getInstance();
|
||||
|
||||
export default {
|
||||
// Get categories for a user
|
||||
async getUserCategories(ctx: Context) {
|
||||
const user = (ctx.state as any).user;
|
||||
const userId = user.id;
|
||||
|
||||
try {
|
||||
const categoriesCollection = db.getCollection<Category>(dbConfig.collections.categories);
|
||||
|
||||
const categories = await categoriesCollection.find({
|
||||
userId: new ObjectId(userId)
|
||||
}).toArray();
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
data: categories
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.status = 500;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Error retrieving categories',
|
||||
error: (error as Error).message
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Add new category
|
||||
async addCategory(ctx: Context) {
|
||||
const user = (ctx.state as any).user;
|
||||
const userId = user.id;
|
||||
const { name, icon, color } = ctx.request.body as {
|
||||
name: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
if (!name) {
|
||||
ctx.status = 400;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Category name is required'
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const categoriesCollection = db.getCollection<Category>(dbConfig.collections.categories);
|
||||
|
||||
// Check if category already exists
|
||||
const existingCategory = await categoriesCollection.findOne({
|
||||
userId: new ObjectId(userId),
|
||||
name
|
||||
});
|
||||
|
||||
if (existingCategory) {
|
||||
ctx.status = 409;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Category already exists'
|
||||
// Get categories for a user
|
||||
async getUserCategories(ctx: Context) {
|
||||
const user = (ctx.state as any).user;
|
||||
const userId = user.id;
|
||||
|
||||
try {
|
||||
const categoriesCollection = db.getCollection<Category>(dbConfig.collections.categories);
|
||||
|
||||
const categories = await categoriesCollection.find({
|
||||
userId: new ObjectId(userId)
|
||||
}).toArray();
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
data: categories
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.status = 500;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Error retrieving categories',
|
||||
error: (error as Error).message
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Add new category
|
||||
async addCategory(ctx: Context) {
|
||||
const user = (ctx.state as any).user;
|
||||
const userId = user.id;
|
||||
const { name, icon, color } = ctx.request.body as {
|
||||
name: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const newCategory: Category = {
|
||||
userId: new ObjectId(userId),
|
||||
name,
|
||||
icon,
|
||||
color,
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
};
|
||||
|
||||
const result = await categoriesCollection.insertOne(newCategory);
|
||||
|
||||
ctx.status = 201;
|
||||
ctx.body = {
|
||||
success: true,
|
||||
message: 'Category added successfully',
|
||||
categoryId: result.insertedId
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.status = 500;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Error adding category',
|
||||
error: (error as Error).message
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Update category
|
||||
async updateCategory(ctx: Context) {
|
||||
const user = (ctx.state as any).user;
|
||||
const userId = user.id;
|
||||
const categoryId = ctx.params.id;
|
||||
|
||||
const { name, icon, color } = ctx.request.body as {
|
||||
name?: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
if (!categoryId || !ObjectId.isValid(categoryId)) {
|
||||
ctx.status = 400;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Valid category ID is required'
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const categoriesCollection = db.getCollection<Category>(dbConfig.collections.categories);
|
||||
|
||||
// Make sure category belongs to user
|
||||
const category = await categoriesCollection.findOne({
|
||||
_id: new ObjectId(categoryId),
|
||||
userId: new ObjectId(userId)
|
||||
});
|
||||
|
||||
if (!category) {
|
||||
ctx.status = 404;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Category not found'
|
||||
|
||||
if (!name) {
|
||||
ctx.status = 400;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Category name is required'
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const categoriesCollection = db.getCollection<Category>(dbConfig.collections.categories);
|
||||
|
||||
// Check if category already exists
|
||||
const existingCategory = await categoriesCollection.findOne({
|
||||
userId: new ObjectId(userId),
|
||||
name
|
||||
});
|
||||
|
||||
if (existingCategory) {
|
||||
ctx.status = 409;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Category already exists'
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const newCategory: Category = {
|
||||
userId: new ObjectId(userId),
|
||||
name,
|
||||
icon,
|
||||
color,
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
};
|
||||
|
||||
const result = await categoriesCollection.insertOne(newCategory);
|
||||
|
||||
ctx.status = 201;
|
||||
ctx.body = {
|
||||
success: true,
|
||||
message: 'Category added successfully',
|
||||
categoryId: result.insertedId
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.status = 500;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Error adding category',
|
||||
error: (error as Error).message
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Update category
|
||||
async updateCategory(ctx: Context) {
|
||||
const user = (ctx.state as any).user;
|
||||
const userId = user.id;
|
||||
const categoryId = ctx.params.id;
|
||||
|
||||
const { name, icon, color } = ctx.request.body as {
|
||||
name?: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData: Partial<Category> = {
|
||||
updatedAt: new Date()
|
||||
};
|
||||
|
||||
if (name) updateData.name = name;
|
||||
if (icon) updateData.icon = icon;
|
||||
if (color) updateData.color = color;
|
||||
|
||||
await categoriesCollection.updateOne(
|
||||
{ _id: new ObjectId(categoryId) },
|
||||
{ $set: updateData }
|
||||
);
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
message: 'Category updated successfully'
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.status = 500;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Error updating category',
|
||||
error: (error as Error).message
|
||||
};
|
||||
|
||||
if (!categoryId || !ObjectId.isValid(categoryId)) {
|
||||
ctx.status = 400;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Valid category ID is required'
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const categoriesCollection = db.getCollection<Category>(dbConfig.collections.categories);
|
||||
|
||||
// Make sure category belongs to user
|
||||
const category = await categoriesCollection.findOne({
|
||||
_id: new ObjectId(categoryId),
|
||||
userId: new ObjectId(userId)
|
||||
});
|
||||
|
||||
if (!category) {
|
||||
ctx.status = 404;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Category not found'
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData: Partial<Category> = {
|
||||
updatedAt: new Date()
|
||||
};
|
||||
|
||||
if (name) updateData.name = name;
|
||||
if (icon) updateData.icon = icon;
|
||||
if (color) updateData.color = color;
|
||||
|
||||
await categoriesCollection.updateOne(
|
||||
{ _id: new ObjectId(categoryId) },
|
||||
{ $set: updateData }
|
||||
);
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
message: 'Category updated successfully'
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.status = 500;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Error updating category',
|
||||
error: (error as Error).message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Context } from 'koa';
|
||||
import DatabaseService from '../services/database';
|
||||
import dbConfig from '../config/database';
|
||||
import type { Transaction } from '../models';
|
||||
import { ObjectId } from 'mongodb';
|
||||
import type { Context } from "koa";
|
||||
import { ObjectId } from "mongodb";
|
||||
import dbConfig from "../config/database";
|
||||
import type { Transaction } from "../models";
|
||||
import DatabaseService from "../services/database";
|
||||
|
||||
const db = DatabaseService.getInstance();
|
||||
|
||||
@@ -11,100 +11,105 @@ export default {
|
||||
async getProtectedData(ctx: Context) {
|
||||
// The user property is added by koa-jwt middleware
|
||||
const user = (ctx.state as any).user;
|
||||
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
message: 'You have access to protected data',
|
||||
message: "You have access to protected data",
|
||||
data: {
|
||||
secret: 'This is protected data',
|
||||
secret: "This is protected data",
|
||||
timestamp: new Date().toISOString(),
|
||||
user: user.username,
|
||||
userId: user.id
|
||||
}
|
||||
userId: user.id,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
// Get user's transactions
|
||||
async getUserTransactions(ctx: Context) {
|
||||
const user = (ctx.state as any).user;
|
||||
const userId = user.id;
|
||||
|
||||
|
||||
try {
|
||||
const transactionsCollection = db.getCollection<Transaction>(dbConfig.collections.transactions);
|
||||
|
||||
const transactionsCollection = db.getCollection<Transaction>(
|
||||
dbConfig.collections.transactions
|
||||
);
|
||||
|
||||
// Get the latest 10 transactions for the user
|
||||
const transactions = await transactionsCollection.find({
|
||||
userId: new ObjectId(userId)
|
||||
})
|
||||
.sort({ date: -1 })
|
||||
.limit(10)
|
||||
.toArray();
|
||||
|
||||
const transactions = await transactionsCollection
|
||||
.find({
|
||||
userId: new ObjectId(String(userId)),
|
||||
})
|
||||
.sort({ date: -1 })
|
||||
.limit(10)
|
||||
.toArray();
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
data: transactions
|
||||
data: transactions,
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.status = 500;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Error retrieving transactions',
|
||||
error: (error as Error).message
|
||||
message: "Error retrieving transactions",
|
||||
error: (error as Error).message,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Add new transaction
|
||||
async addTransaction(ctx: Context) {
|
||||
const user = (ctx.state as any).user;
|
||||
const userId = user.id;
|
||||
const { amount, type, categoryId, description } = ctx.request.body as {
|
||||
amount: number;
|
||||
type: 'income' | 'expense';
|
||||
type: "income" | "expense";
|
||||
categoryId: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
|
||||
// Validate input
|
||||
if (!amount || !type || !categoryId) {
|
||||
ctx.status = 400;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Amount, type and category are required'
|
||||
message: "Amount, type and category are required",
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const transactionsCollection = db.getCollection<Transaction>(dbConfig.collections.transactions);
|
||||
|
||||
const transactionsCollection = db.getCollection<Transaction>(
|
||||
dbConfig.collections.transactions
|
||||
);
|
||||
|
||||
const now = new Date();
|
||||
const newTransaction: Transaction = {
|
||||
userId: new ObjectId(userId),
|
||||
amount,
|
||||
type,
|
||||
categoryId: new ObjectId(categoryId),
|
||||
description: description || '',
|
||||
description: description || "",
|
||||
date: now,
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
|
||||
const result = await transactionsCollection.insertOne(newTransaction);
|
||||
|
||||
|
||||
ctx.status = 201;
|
||||
ctx.body = {
|
||||
success: true,
|
||||
message: 'Transaction added successfully',
|
||||
transactionId: result.insertedId
|
||||
message: "Transaction added successfully",
|
||||
transactionId: result.insertedId,
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.status = 500;
|
||||
ctx.body = {
|
||||
success: false,
|
||||
message: 'Error adding transaction',
|
||||
error: (error as Error).message
|
||||
message: "Error adding transaction",
|
||||
error: (error as Error).message,
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ export const authenticate: Middleware = jwt({
|
||||
// Error handling middleware for JWT authentication
|
||||
export const handleJwtError: Middleware = async (ctx, next) => {
|
||||
try {
|
||||
// Next middleware have to be jwt call.
|
||||
await next();
|
||||
} catch (err: any) {
|
||||
if (err.status === 401) {
|
||||
|
||||
@@ -5,11 +5,19 @@ import categoriesController from '../controllers/categories';
|
||||
|
||||
const router = new Router({ prefix: '/api' });
|
||||
|
||||
/*
|
||||
Middleware requires authentication for all routes.
|
||||
Exceptions are defined in the auth middleware `backend/src/middleware/auth.ts`
|
||||
There are defined exceptions for auth routes
|
||||
*/
|
||||
|
||||
// --- Public routes ---
|
||||
// Auth routes
|
||||
router.post('/auth/register', authController.register);
|
||||
router.post('/auth/register-admin', authController.registerAdmin);
|
||||
router.post('/auth/login', authController.login);
|
||||
|
||||
// --- All below routes require authentication ----
|
||||
// Protected routes
|
||||
router.get('/protected', protectedController.getProtectedData);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user