fix
This commit is contained in:
15
.env
Normal file
15
.env
Normal file
@ -0,0 +1,15 @@
|
||||
# Environment variables declared in this file are automatically made available to Prisma.
|
||||
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
|
||||
|
||||
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
|
||||
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
||||
|
||||
# Database Configuration
|
||||
DATABASE_HOST=localhost
|
||||
DATABASE_USER=root
|
||||
DATABASE_PASSWORD=
|
||||
DATABASE_NAME=deployer
|
||||
|
||||
# API Server
|
||||
PORT=3000
|
||||
NODE_ENV=development
|
||||
44
api/auth.js
Normal file
44
api/auth.js
Normal file
@ -0,0 +1,44 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const bodyParser = require('body-parser');
|
||||
const { authenticateUser } = require('../src/services/authService');
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3001;
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(bodyParser.json());
|
||||
|
||||
// Authentication endpoint
|
||||
app.post('/api/auth/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Email and password are required'
|
||||
});
|
||||
}
|
||||
|
||||
const result = await authenticateUser(email, password);
|
||||
|
||||
if (result.success) {
|
||||
return res.json(result);
|
||||
} else {
|
||||
return res.status(401).json(result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Authentication error:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Start server
|
||||
app.listen(port, () => {
|
||||
console.log(`Authentication API running on port ${port}`);
|
||||
});
|
||||
888
package-lock.json
generated
888
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@ -10,17 +10,14 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fullcalendar/core": "^6.1.17",
|
||||
"@fullcalendar/daygrid": "^6.1.17",
|
||||
"@fullcalendar/interaction": "^6.1.17",
|
||||
"@fullcalendar/list": "^6.1.17",
|
||||
"@fullcalendar/react": "^6.1.17",
|
||||
"@fullcalendar/timegrid": "^6.1.17",
|
||||
"@react-jvectormap/core": "^1.0.4",
|
||||
"@react-jvectormap/world": "^1.1.2",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"apexcharts": "^4.5.0",
|
||||
"axios": "^1.8.4",
|
||||
"bcrypt": "^5.1.1",
|
||||
"clsx": "^2.1.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"flatpickr": "^4.6.13",
|
||||
"react": "^19.0.0",
|
||||
"react-apexcharts": "^1.7.0",
|
||||
@ -28,7 +25,6 @@
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-helmet-async": "^2.0.5",
|
||||
"react-router-dom": "^7.5.0",
|
||||
"swiper": "^11.2.6",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
@ -44,6 +40,7 @@
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^15.15.0",
|
||||
"prisma": "^6.6.0",
|
||||
"tailwindcss": "^4.1.3",
|
||||
"typescript": "~5.7.2",
|
||||
"typescript-eslint": "^8.24.1",
|
||||
|
||||
44
prisma/migrations/20250413123649_init/migration.sql
Normal file
44
prisma/migrations/20250413123649_init/migration.sql
Normal file
@ -0,0 +1,44 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE `Project` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
`repository` VARCHAR(191) NOT NULL,
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updatedAt` DATETIME(3) NOT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Deployment` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`projectId` INTEGER NOT NULL,
|
||||
`environmentId` INTEGER NOT NULL,
|
||||
`status` VARCHAR(191) NOT NULL DEFAULT 'pending',
|
||||
`commitHash` VARCHAR(191) NOT NULL,
|
||||
`deployedAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`logs` TEXT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Environment` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
`url` VARCHAR(191) NOT NULL,
|
||||
`projectId` INTEGER NOT NULL,
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updatedAt` DATETIME(3) NOT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Deployment` ADD CONSTRAINT `Deployment_projectId_fkey` FOREIGN KEY (`projectId`) REFERENCES `Project`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Deployment` ADD CONSTRAINT `Deployment_environmentId_fkey` FOREIGN KEY (`environmentId`) REFERENCES `Environment`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Environment` ADD CONSTRAINT `Environment_projectId_fkey` FOREIGN KEY (`projectId`) REFERENCES `Project`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@ -0,0 +1,18 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE `User` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`email` VARCHAR(191) NOT NULL,
|
||||
`password` VARCHAR(191) NOT NULL,
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
`surname` VARCHAR(191) NOT NULL,
|
||||
`active` BOOLEAN NOT NULL DEFAULT true,
|
||||
`role` VARCHAR(191) NOT NULL DEFAULT 'user',
|
||||
`lastLogin` DATETIME(3) NULL,
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updatedAt` DATETIME(3) NOT NULL,
|
||||
`resetToken` VARCHAR(191) NULL,
|
||||
`resetTokenExpiry` DATETIME(3) NULL,
|
||||
|
||||
UNIQUE INDEX `User_email_key`(`email`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
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 (e.g., Git)
|
||||
provider = "mysql"
|
||||
63
prisma/schema.prisma
Normal file
63
prisma/schema.prisma
Normal file
@ -0,0 +1,63 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
|
||||
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "../src/generated/prisma"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "mysql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Project {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
repository String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deployments Deployment[]
|
||||
environments Environment[]
|
||||
}
|
||||
|
||||
model Deployment {
|
||||
id Int @id @default(autoincrement())
|
||||
projectId Int
|
||||
project Project @relation(fields: [projectId], references: [id])
|
||||
environmentId Int
|
||||
environment Environment @relation(fields: [environmentId], references: [id])
|
||||
status String @default("pending") // pending, success, failed
|
||||
commitHash String
|
||||
deployedAt DateTime @default(now())
|
||||
logs String? @db.Text
|
||||
}
|
||||
|
||||
model Environment {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
url String
|
||||
projectId Int
|
||||
project Project @relation(fields: [projectId], references: [id])
|
||||
deployments Deployment[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
email String @unique
|
||||
password String
|
||||
name String
|
||||
surname String
|
||||
active Boolean @default(true)
|
||||
role String @default("user") // user, admin, etc.
|
||||
lastLogin DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
resetToken String?
|
||||
resetTokenExpiry DateTime?
|
||||
}
|
||||
105
public/api/login.js
Normal file
105
public/api/login.js
Normal file
@ -0,0 +1,105 @@
|
||||
const express = require('express');
|
||||
const mysql = require('mysql2/promise');
|
||||
const router = express.Router();
|
||||
const crypto = require('crypto-js');
|
||||
require('dotenv').config();
|
||||
|
||||
// Create connection pool to MySQL database
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DATABASE_HOST || 'localhost',
|
||||
user: process.env.DATABASE_USER || 'root',
|
||||
password: process.env.DATABASE_PASSWORD || '',
|
||||
database: process.env.DATABASE_NAME || 'deployer',
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0,
|
||||
});
|
||||
|
||||
// Generate a secure token
|
||||
const generateToken = (length = 64) => {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let token = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
token += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
return token;
|
||||
};
|
||||
|
||||
/**
|
||||
* Login endpoint to authenticate users against the database
|
||||
*/
|
||||
router.post('/', async (req, res) => {
|
||||
const { email, password } = req.body;
|
||||
|
||||
// Basic validation
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Email and password are required'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Query database for user with matching email
|
||||
const [rows] = await pool.query(
|
||||
'SELECT * FROM User WHERE email = ?',
|
||||
[email]
|
||||
);
|
||||
|
||||
// Check if user exists
|
||||
if (rows.length === 0) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'User not found'
|
||||
});
|
||||
}
|
||||
|
||||
const user = rows[0];
|
||||
|
||||
// Check if user is active
|
||||
if (!user.active) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Account is inactive'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify password (compare hashed passwords)
|
||||
if (user.password !== password) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
// Generate token
|
||||
const token = generateToken();
|
||||
|
||||
// Update last login timestamp
|
||||
await pool.query(
|
||||
'UPDATE User SET lastLogin = NOW() WHERE id = ?',
|
||||
[user.id]
|
||||
);
|
||||
|
||||
// Return success with token and user info
|
||||
return res.json({
|
||||
success: true,
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
name: `${user.name} ${user.surname}`,
|
||||
email: user.email,
|
||||
role: user.role
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Authentication error:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
34
public/api/server.js
Normal file
34
public/api/server.js
Normal file
@ -0,0 +1,34 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const bodyParser = require('body-parser');
|
||||
const loginRoutes = require('./login');
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(bodyParser.json());
|
||||
|
||||
// Routes
|
||||
app.use('/api/login', loginRoutes);
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date() });
|
||||
});
|
||||
|
||||
// Error handling middleware
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('API error:', err);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error',
|
||||
message: process.env.NODE_ENV === 'development' ? err.message : undefined
|
||||
});
|
||||
});
|
||||
|
||||
// Start server
|
||||
app.listen(port, () => {
|
||||
console.log(`API server running on port ${port}`);
|
||||
});
|
||||
29
scripts/add-user.js
Normal file
29
scripts/add-user.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function createUser() {
|
||||
try {
|
||||
const hashedPassword = await bcrypt.hash('gk1510!', 10);
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email: 'gregor@klevze.si',
|
||||
password: hashedPassword,
|
||||
name: 'Gregor',
|
||||
surname: 'Klevže',
|
||||
active: true,
|
||||
role: 'admin',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('User created successfully:', user);
|
||||
} catch (error) {
|
||||
console.error('Error creating user:', error);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
createUser();
|
||||
23
scripts/add-user.sql
Normal file
23
scripts/add-user.sql
Normal file
@ -0,0 +1,23 @@
|
||||
-- Add user: Gregor Klevže
|
||||
-- Note: In a real application, you would use proper password hashing,
|
||||
-- but for direct SQL insertion we're using a placeholder password that you should change immediately
|
||||
INSERT INTO `User` (
|
||||
`email`,
|
||||
`password`,
|
||||
`name`,
|
||||
`surname`,
|
||||
`active`,
|
||||
`role`,
|
||||
`createdAt`,
|
||||
`updatedAt`
|
||||
)
|
||||
VALUES (
|
||||
'gregor@klevze.si',
|
||||
'gk1510!', -- This should be hashed in a real application
|
||||
'Gregor',
|
||||
'Klevže',
|
||||
true,
|
||||
'admin',
|
||||
NOW(),
|
||||
NOW()
|
||||
);
|
||||
29
scripts/add-user.ts
Normal file
29
scripts/add-user.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { PrismaClient } from '../src/generated/prisma';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function createUser() {
|
||||
try {
|
||||
const hashedPassword = await bcrypt.hash('gk1510!', 10);
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email: 'gregor@klevze.si',
|
||||
password: hashedPassword,
|
||||
name: 'Gregor',
|
||||
surname: 'Klevže',
|
||||
active: true,
|
||||
role: 'admin',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('User created successfully:', user);
|
||||
} catch (error) {
|
||||
console.error('Error creating user:', error);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
createUser();
|
||||
@ -11,7 +11,6 @@ import Avatars from "./pages/UiElements/Avatars";
|
||||
import Buttons from "./pages/UiElements/Buttons";
|
||||
import LineChart from "./pages/Charts/LineChart";
|
||||
import BarChart from "./pages/Charts/BarChart";
|
||||
import Calendar from "./pages/Calendar";
|
||||
import BasicTables from "./pages/Tables/BasicTables";
|
||||
import FormElements from "./pages/Forms/FormElements";
|
||||
import Blank from "./pages/Blank";
|
||||
@ -31,7 +30,6 @@ export default function App() {
|
||||
|
||||
{/* Others Page */}
|
||||
<Route path="/profile" element={<UserProfiles />} />
|
||||
<Route path="/calendar" element={<Calendar />} />
|
||||
<Route path="/blank" element={<Blank />} />
|
||||
|
||||
{/* Forms */}
|
||||
@ -46,7 +44,6 @@ export default function App() {
|
||||
<Route path="/badge" element={<Badges />} />
|
||||
<Route path="/buttons" element={<Buttons />} />
|
||||
<Route path="/images" element={<Images />} />
|
||||
<Route path="/videos" element={<Videos />} />
|
||||
|
||||
{/* Charts */}
|
||||
<Route path="/line-chart" element={<LineChart />} />
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { API_BASE_URL } from '../../config/apiConfig';
|
||||
import { API_BASE_URL, isLocalMode } from '../../config/apiConfig';
|
||||
import { localLogin } from '../../services/localApi';
|
||||
|
||||
// Import crypto library for hashing
|
||||
import crypto from 'crypto-js';
|
||||
|
||||
const Login: React.FC = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
@ -15,35 +19,57 @@ const Login: React.FC = () => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
// Send credentials as JSON
|
||||
const response = await fetch(`${API_BASE_URL}/api/login.php`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
}),
|
||||
});
|
||||
// Create a SHA-256 hash of the password
|
||||
const hashedPassword = crypto.SHA256(password).toString();
|
||||
|
||||
const data = await response.json();
|
||||
// Choose between local and remote API based on configuration
|
||||
if (isLocalMode()) {
|
||||
// Use local authentication
|
||||
const result = await localLogin(email, hashedPassword);
|
||||
|
||||
if (response.ok) {
|
||||
// If response is successful, we should have a token
|
||||
if (data.token) {
|
||||
login(data.token);
|
||||
if (result.success && result.token) {
|
||||
login(result.token);
|
||||
} else {
|
||||
setError('Invalid server response');
|
||||
setError(result.error || 'Authentication failed');
|
||||
}
|
||||
} else {
|
||||
// Handle error responses
|
||||
setError(data.error || 'Authentication failed');
|
||||
// Use remote authentication (original implementation)
|
||||
const response = await fetch(`${API_BASE_URL}/api/login.php`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
// Add security headers
|
||||
'X-Requested-With': 'XMLHttpRequest', // Help prevent CSRF
|
||||
},
|
||||
// Send the plaintext email but hashed password
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password: hashedPassword,
|
||||
}),
|
||||
// Include credentials like cookies if needed
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
// If response is successful, we should have a token
|
||||
if (data.token) {
|
||||
login(data.token);
|
||||
} else {
|
||||
setError('Invalid server response');
|
||||
}
|
||||
} else {
|
||||
// Handle error responses
|
||||
setError(data.error || 'Authentication failed');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Failed to connect to the server. Please try again.');
|
||||
console.error('Login error:', err);
|
||||
} finally {
|
||||
// Clear password from memory for security
|
||||
setPassword('');
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
@ -90,6 +116,13 @@ const Login: React.FC = () => {
|
||||
>
|
||||
{isLoading ? 'Logging in...' : 'Login'}
|
||||
</button>
|
||||
{isLocalMode() && (
|
||||
<div className="mt-4 text-sm text-gray-500">
|
||||
<p>Demo accounts:</p>
|
||||
<p>Email: admin@example.com / Password: admin123</p>
|
||||
<p>Email: user@example.com / Password: user123</p>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
57
src/components/common/PageHead.tsx
Normal file
57
src/components/common/PageHead.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import { useEffect, createContext, useContext } from 'react';
|
||||
|
||||
type HeadProps = {
|
||||
title?: string;
|
||||
description?: string;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
// Create a context to manage document head
|
||||
const HeadContext = createContext<{
|
||||
updateHead: (props: HeadProps) => void;
|
||||
}>({
|
||||
updateHead: () => {},
|
||||
});
|
||||
|
||||
// Provider component that will wrap the application
|
||||
export const HeadProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const updateHead = ({ title, description }: HeadProps) => {
|
||||
if (title) {
|
||||
document.title = title;
|
||||
}
|
||||
|
||||
if (description) {
|
||||
// Find existing description meta tag or create a new one
|
||||
let metaDescription = document.querySelector('meta[name="description"]');
|
||||
if (!metaDescription) {
|
||||
metaDescription = document.createElement('meta');
|
||||
metaDescription.setAttribute('name', 'description');
|
||||
document.head.appendChild(metaDescription);
|
||||
}
|
||||
metaDescription.setAttribute('content', description);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<HeadContext.Provider value={{ updateHead }}>
|
||||
{children}
|
||||
</HeadContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// Component to use in pages for setting title and description
|
||||
const PageHead = ({ title, description }: HeadProps) => {
|
||||
const { updateHead } = useContext(HeadContext);
|
||||
|
||||
useEffect(() => {
|
||||
updateHead({ title, description });
|
||||
|
||||
// Cleanup is not typically needed, but could reset title if desired
|
||||
return () => {};
|
||||
}, [title, description, updateHead]);
|
||||
|
||||
// This component doesn't render anything
|
||||
return null;
|
||||
};
|
||||
|
||||
export default PageHead;
|
||||
@ -1,4 +1,4 @@
|
||||
import { HelmetProvider, Helmet } from "react-helmet-async";
|
||||
import PageHead from './PageHead';
|
||||
|
||||
const PageMeta = ({
|
||||
title,
|
||||
@ -7,14 +7,12 @@ const PageMeta = ({
|
||||
title: string;
|
||||
description: string;
|
||||
}) => (
|
||||
<Helmet>
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={description} />
|
||||
</Helmet>
|
||||
<PageHead title={title} description={description} />
|
||||
);
|
||||
|
||||
// This is just for backward compatibility
|
||||
export const AppWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<HelmetProvider>{children}</HelmetProvider>
|
||||
<>{children}</>
|
||||
);
|
||||
|
||||
export default PageMeta;
|
||||
|
||||
@ -1,2 +1,9 @@
|
||||
// Base URL configuration for API endpoints
|
||||
export const API_BASE_URL = 'https://deploy.projekti.info';
|
||||
|
||||
// Environment configuration - simple string setting
|
||||
export const API_MODE: string = 'local'; // 'local' or 'remote'
|
||||
|
||||
// Helper functions
|
||||
export const isLocalMode = () => API_MODE === 'local';
|
||||
export const isRemoteMode = () => API_MODE === 'remote';
|
||||
1
src/generated/prisma/client.d.ts
vendored
Normal file
1
src/generated/prisma/client.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export * from "./index"
|
||||
1
src/generated/prisma/client.js
Normal file
1
src/generated/prisma/client.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = { ...require('.') }
|
||||
1
src/generated/prisma/default.d.ts
vendored
Normal file
1
src/generated/prisma/default.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export * from "./index"
|
||||
1
src/generated/prisma/default.js
Normal file
1
src/generated/prisma/default.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = { ...require('.') }
|
||||
1
src/generated/prisma/edge.d.ts
vendored
Normal file
1
src/generated/prisma/edge.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export * from "./default"
|
||||
248
src/generated/prisma/edge.js
Normal file
248
src/generated/prisma/edge.js
Normal file
File diff suppressed because one or more lines are too long
234
src/generated/prisma/index-browser.js
Normal file
234
src/generated/prisma/index-browser.js
Normal file
@ -0,0 +1,234 @@
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
|
||||
const {
|
||||
Decimal,
|
||||
objectEnumValues,
|
||||
makeStrictEnum,
|
||||
Public,
|
||||
getRuntime,
|
||||
skip
|
||||
} = require('./runtime/index-browser.js')
|
||||
|
||||
|
||||
const Prisma = {}
|
||||
|
||||
exports.Prisma = Prisma
|
||||
exports.$Enums = {}
|
||||
|
||||
/**
|
||||
* Prisma Client JS version: 6.6.0
|
||||
* Query Engine version: f676762280b54cd07c770017ed3711ddde35f37a
|
||||
*/
|
||||
Prisma.prismaVersion = {
|
||||
client: "6.6.0",
|
||||
engine: "f676762280b54cd07c770017ed3711ddde35f37a"
|
||||
}
|
||||
|
||||
Prisma.PrismaClientKnownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)};
|
||||
Prisma.PrismaClientUnknownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientRustPanicError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientInitializationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientValidationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.Decimal = Decimal
|
||||
|
||||
/**
|
||||
* Re-export of sql-template-tag
|
||||
*/
|
||||
Prisma.sql = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.empty = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.join = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.raw = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.validator = Public.validator
|
||||
|
||||
/**
|
||||
* Extensions
|
||||
*/
|
||||
Prisma.getExtensionContext = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.defineExtension = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
|
||||
/**
|
||||
* Shorthand utilities for JSON filtering
|
||||
*/
|
||||
Prisma.DbNull = objectEnumValues.instances.DbNull
|
||||
Prisma.JsonNull = objectEnumValues.instances.JsonNull
|
||||
Prisma.AnyNull = objectEnumValues.instances.AnyNull
|
||||
|
||||
Prisma.NullTypes = {
|
||||
DbNull: objectEnumValues.classes.DbNull,
|
||||
JsonNull: objectEnumValues.classes.JsonNull,
|
||||
AnyNull: objectEnumValues.classes.AnyNull
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Enums
|
||||
*/
|
||||
|
||||
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||
ReadUncommitted: 'ReadUncommitted',
|
||||
ReadCommitted: 'ReadCommitted',
|
||||
RepeatableRead: 'RepeatableRead',
|
||||
Serializable: 'Serializable'
|
||||
});
|
||||
|
||||
exports.Prisma.ProjectScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
repository: 'repository',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.DeploymentScalarFieldEnum = {
|
||||
id: 'id',
|
||||
projectId: 'projectId',
|
||||
environmentId: 'environmentId',
|
||||
status: 'status',
|
||||
commitHash: 'commitHash',
|
||||
deployedAt: 'deployedAt',
|
||||
logs: 'logs'
|
||||
};
|
||||
|
||||
exports.Prisma.EnvironmentScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
url: 'url',
|
||||
projectId: 'projectId',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.UserScalarFieldEnum = {
|
||||
id: 'id',
|
||||
email: 'email',
|
||||
password: 'password',
|
||||
name: 'name',
|
||||
surname: 'surname',
|
||||
active: 'active',
|
||||
role: 'role',
|
||||
lastLogin: 'lastLogin',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
resetToken: 'resetToken',
|
||||
resetTokenExpiry: 'resetTokenExpiry'
|
||||
};
|
||||
|
||||
exports.Prisma.SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
};
|
||||
|
||||
exports.Prisma.ProjectOrderByRelevanceFieldEnum = {
|
||||
name: 'name',
|
||||
repository: 'repository'
|
||||
};
|
||||
|
||||
exports.Prisma.NullsOrder = {
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
};
|
||||
|
||||
exports.Prisma.DeploymentOrderByRelevanceFieldEnum = {
|
||||
status: 'status',
|
||||
commitHash: 'commitHash',
|
||||
logs: 'logs'
|
||||
};
|
||||
|
||||
exports.Prisma.EnvironmentOrderByRelevanceFieldEnum = {
|
||||
name: 'name',
|
||||
url: 'url'
|
||||
};
|
||||
|
||||
exports.Prisma.UserOrderByRelevanceFieldEnum = {
|
||||
email: 'email',
|
||||
password: 'password',
|
||||
name: 'name',
|
||||
surname: 'surname',
|
||||
role: 'role',
|
||||
resetToken: 'resetToken'
|
||||
};
|
||||
|
||||
|
||||
exports.Prisma.ModelName = {
|
||||
Project: 'Project',
|
||||
Deployment: 'Deployment',
|
||||
Environment: 'Environment',
|
||||
User: 'User'
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a stub Prisma Client that will error at runtime if called.
|
||||
*/
|
||||
class PrismaClient {
|
||||
constructor() {
|
||||
return new Proxy(this, {
|
||||
get(target, prop) {
|
||||
let message
|
||||
const runtime = getRuntime()
|
||||
if (runtime.isEdge) {
|
||||
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
|
||||
- Use Prisma Accelerate: https://pris.ly/d/accelerate
|
||||
- Use Driver Adapters: https://pris.ly/d/driver-adapters
|
||||
`;
|
||||
} else {
|
||||
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
|
||||
}
|
||||
|
||||
message += `
|
||||
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
|
||||
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.PrismaClient = PrismaClient
|
||||
|
||||
Object.assign(exports, Prisma)
|
||||
7115
src/generated/prisma/index.d.ts
vendored
Normal file
7115
src/generated/prisma/index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
269
src/generated/prisma/index.js
Normal file
269
src/generated/prisma/index.js
Normal file
File diff suppressed because one or more lines are too long
140
src/generated/prisma/package.json
Normal file
140
src/generated/prisma/package.json
Normal file
@ -0,0 +1,140 @@
|
||||
{
|
||||
"name": "prisma-client-17e7e976046b9652083f42b4ee182a45cd98ff19613dd50aeb188ab5b9600242",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"browser": "index-browser.js",
|
||||
"exports": {
|
||||
"./client": {
|
||||
"require": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"import": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"require": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"import": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./edge": {
|
||||
"types": "./edge.d.ts",
|
||||
"require": "./edge.js",
|
||||
"import": "./edge.js",
|
||||
"default": "./edge.js"
|
||||
},
|
||||
"./react-native": {
|
||||
"types": "./react-native.d.ts",
|
||||
"require": "./react-native.js",
|
||||
"import": "./react-native.js",
|
||||
"default": "./react-native.js"
|
||||
},
|
||||
"./extension": {
|
||||
"types": "./extension.d.ts",
|
||||
"require": "./extension.js",
|
||||
"import": "./extension.js",
|
||||
"default": "./extension.js"
|
||||
},
|
||||
"./index-browser": {
|
||||
"types": "./index.d.ts",
|
||||
"require": "./index-browser.js",
|
||||
"import": "./index-browser.js",
|
||||
"default": "./index-browser.js"
|
||||
},
|
||||
"./index": {
|
||||
"types": "./index.d.ts",
|
||||
"require": "./index.js",
|
||||
"import": "./index.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./wasm": {
|
||||
"types": "./wasm.d.ts",
|
||||
"require": "./wasm.js",
|
||||
"import": "./wasm.mjs",
|
||||
"default": "./wasm.mjs"
|
||||
},
|
||||
"./runtime/client": {
|
||||
"types": "./runtime/client.d.ts",
|
||||
"require": "./runtime/client.js",
|
||||
"import": "./runtime/client.mjs",
|
||||
"default": "./runtime/client.mjs"
|
||||
},
|
||||
"./runtime/library": {
|
||||
"types": "./runtime/library.d.ts",
|
||||
"require": "./runtime/library.js",
|
||||
"import": "./runtime/library.mjs",
|
||||
"default": "./runtime/library.mjs"
|
||||
},
|
||||
"./runtime/binary": {
|
||||
"types": "./runtime/binary.d.ts",
|
||||
"require": "./runtime/binary.js",
|
||||
"import": "./runtime/binary.mjs",
|
||||
"default": "./runtime/binary.mjs"
|
||||
},
|
||||
"./runtime/wasm": {
|
||||
"types": "./runtime/wasm.d.ts",
|
||||
"require": "./runtime/wasm.js",
|
||||
"import": "./runtime/wasm.mjs",
|
||||
"default": "./runtime/wasm.mjs"
|
||||
},
|
||||
"./runtime/edge": {
|
||||
"types": "./runtime/edge.d.ts",
|
||||
"require": "./runtime/edge.js",
|
||||
"import": "./runtime/edge-esm.js",
|
||||
"default": "./runtime/edge-esm.js"
|
||||
},
|
||||
"./runtime/react-native": {
|
||||
"types": "./runtime/react-native.d.ts",
|
||||
"require": "./runtime/react-native.js",
|
||||
"import": "./runtime/react-native.js",
|
||||
"default": "./runtime/react-native.js"
|
||||
},
|
||||
"./generator-build": {
|
||||
"require": "./generator-build/index.js",
|
||||
"import": "./generator-build/index.js",
|
||||
"default": "./generator-build/index.js"
|
||||
},
|
||||
"./sql": {
|
||||
"require": {
|
||||
"types": "./sql.d.ts",
|
||||
"node": "./sql.js",
|
||||
"default": "./sql.js"
|
||||
},
|
||||
"import": {
|
||||
"types": "./sql.d.ts",
|
||||
"node": "./sql.mjs",
|
||||
"default": "./sql.mjs"
|
||||
},
|
||||
"default": "./sql.js"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"version": "6.6.0",
|
||||
"sideEffects": false
|
||||
}
|
||||
BIN
src/generated/prisma/query_engine-windows.dll.node
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node
Normal file
Binary file not shown.
31
src/generated/prisma/runtime/edge-esm.js
Normal file
31
src/generated/prisma/runtime/edge-esm.js
Normal file
File diff suppressed because one or more lines are too long
31
src/generated/prisma/runtime/edge.js
Normal file
31
src/generated/prisma/runtime/edge.js
Normal file
File diff suppressed because one or more lines are too long
370
src/generated/prisma/runtime/index-browser.d.ts
vendored
Normal file
370
src/generated/prisma/runtime/index-browser.d.ts
vendored
Normal file
@ -0,0 +1,370 @@
|
||||
declare class AnyNull extends NullTypesEnumValue {
|
||||
private readonly _brand_AnyNull;
|
||||
}
|
||||
|
||||
declare type Args<T, F extends Operation> = T extends {
|
||||
[K: symbol]: {
|
||||
types: {
|
||||
operations: {
|
||||
[K in F]: {
|
||||
args: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} ? T[symbol]['types']['operations'][F]['args'] : any;
|
||||
|
||||
declare class DbNull extends NullTypesEnumValue {
|
||||
private readonly _brand_DbNull;
|
||||
}
|
||||
|
||||
export declare function Decimal(n: Decimal.Value): Decimal;
|
||||
|
||||
export declare namespace Decimal {
|
||||
export type Constructor = typeof Decimal;
|
||||
export type Instance = Decimal;
|
||||
export type Rounding = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
||||
export type Modulo = Rounding | 9;
|
||||
export type Value = string | number | Decimal;
|
||||
|
||||
// http://mikemcl.github.io/decimal.js/#constructor-properties
|
||||
export interface Config {
|
||||
precision?: number;
|
||||
rounding?: Rounding;
|
||||
toExpNeg?: number;
|
||||
toExpPos?: number;
|
||||
minE?: number;
|
||||
maxE?: number;
|
||||
crypto?: boolean;
|
||||
modulo?: Modulo;
|
||||
defaults?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export declare class Decimal {
|
||||
readonly d: number[];
|
||||
readonly e: number;
|
||||
readonly s: number;
|
||||
|
||||
constructor(n: Decimal.Value);
|
||||
|
||||
absoluteValue(): Decimal;
|
||||
abs(): Decimal;
|
||||
|
||||
ceil(): Decimal;
|
||||
|
||||
clampedTo(min: Decimal.Value, max: Decimal.Value): Decimal;
|
||||
clamp(min: Decimal.Value, max: Decimal.Value): Decimal;
|
||||
|
||||
comparedTo(n: Decimal.Value): number;
|
||||
cmp(n: Decimal.Value): number;
|
||||
|
||||
cosine(): Decimal;
|
||||
cos(): Decimal;
|
||||
|
||||
cubeRoot(): Decimal;
|
||||
cbrt(): Decimal;
|
||||
|
||||
decimalPlaces(): number;
|
||||
dp(): number;
|
||||
|
||||
dividedBy(n: Decimal.Value): Decimal;
|
||||
div(n: Decimal.Value): Decimal;
|
||||
|
||||
dividedToIntegerBy(n: Decimal.Value): Decimal;
|
||||
divToInt(n: Decimal.Value): Decimal;
|
||||
|
||||
equals(n: Decimal.Value): boolean;
|
||||
eq(n: Decimal.Value): boolean;
|
||||
|
||||
floor(): Decimal;
|
||||
|
||||
greaterThan(n: Decimal.Value): boolean;
|
||||
gt(n: Decimal.Value): boolean;
|
||||
|
||||
greaterThanOrEqualTo(n: Decimal.Value): boolean;
|
||||
gte(n: Decimal.Value): boolean;
|
||||
|
||||
hyperbolicCosine(): Decimal;
|
||||
cosh(): Decimal;
|
||||
|
||||
hyperbolicSine(): Decimal;
|
||||
sinh(): Decimal;
|
||||
|
||||
hyperbolicTangent(): Decimal;
|
||||
tanh(): Decimal;
|
||||
|
||||
inverseCosine(): Decimal;
|
||||
acos(): Decimal;
|
||||
|
||||
inverseHyperbolicCosine(): Decimal;
|
||||
acosh(): Decimal;
|
||||
|
||||
inverseHyperbolicSine(): Decimal;
|
||||
asinh(): Decimal;
|
||||
|
||||
inverseHyperbolicTangent(): Decimal;
|
||||
atanh(): Decimal;
|
||||
|
||||
inverseSine(): Decimal;
|
||||
asin(): Decimal;
|
||||
|
||||
inverseTangent(): Decimal;
|
||||
atan(): Decimal;
|
||||
|
||||
isFinite(): boolean;
|
||||
|
||||
isInteger(): boolean;
|
||||
isInt(): boolean;
|
||||
|
||||
isNaN(): boolean;
|
||||
|
||||
isNegative(): boolean;
|
||||
isNeg(): boolean;
|
||||
|
||||
isPositive(): boolean;
|
||||
isPos(): boolean;
|
||||
|
||||
isZero(): boolean;
|
||||
|
||||
lessThan(n: Decimal.Value): boolean;
|
||||
lt(n: Decimal.Value): boolean;
|
||||
|
||||
lessThanOrEqualTo(n: Decimal.Value): boolean;
|
||||
lte(n: Decimal.Value): boolean;
|
||||
|
||||
logarithm(n?: Decimal.Value): Decimal;
|
||||
log(n?: Decimal.Value): Decimal;
|
||||
|
||||
minus(n: Decimal.Value): Decimal;
|
||||
sub(n: Decimal.Value): Decimal;
|
||||
|
||||
modulo(n: Decimal.Value): Decimal;
|
||||
mod(n: Decimal.Value): Decimal;
|
||||
|
||||
naturalExponential(): Decimal;
|
||||
exp(): Decimal;
|
||||
|
||||
naturalLogarithm(): Decimal;
|
||||
ln(): Decimal;
|
||||
|
||||
negated(): Decimal;
|
||||
neg(): Decimal;
|
||||
|
||||
plus(n: Decimal.Value): Decimal;
|
||||
add(n: Decimal.Value): Decimal;
|
||||
|
||||
precision(includeZeros?: boolean): number;
|
||||
sd(includeZeros?: boolean): number;
|
||||
|
||||
round(): Decimal;
|
||||
|
||||
sine() : Decimal;
|
||||
sin() : Decimal;
|
||||
|
||||
squareRoot(): Decimal;
|
||||
sqrt(): Decimal;
|
||||
|
||||
tangent() : Decimal;
|
||||
tan() : Decimal;
|
||||
|
||||
times(n: Decimal.Value): Decimal;
|
||||
mul(n: Decimal.Value) : Decimal;
|
||||
|
||||
toBinary(significantDigits?: number): string;
|
||||
toBinary(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toDecimalPlaces(decimalPlaces?: number): Decimal;
|
||||
toDecimalPlaces(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
|
||||
toDP(decimalPlaces?: number): Decimal;
|
||||
toDP(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
|
||||
|
||||
toExponential(decimalPlaces?: number): string;
|
||||
toExponential(decimalPlaces: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toFixed(decimalPlaces?: number): string;
|
||||
toFixed(decimalPlaces: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toFraction(max_denominator?: Decimal.Value): Decimal[];
|
||||
|
||||
toHexadecimal(significantDigits?: number): string;
|
||||
toHexadecimal(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
toHex(significantDigits?: number): string;
|
||||
toHex(significantDigits: number, rounding?: Decimal.Rounding): string;
|
||||
|
||||
toJSON(): string;
|
||||
|
||||
toNearest(n: Decimal.Value, rounding?: Decimal.Rounding): Decimal;
|
||||
|
||||
toNumber(): number;
|
||||
|
||||
toOctal(significantDigits?: number): string;
|
||||
toOctal(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toPower(n: Decimal.Value): Decimal;
|
||||
pow(n: Decimal.Value): Decimal;
|
||||
|
||||
toPrecision(significantDigits?: number): string;
|
||||
toPrecision(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toSignificantDigits(significantDigits?: number): Decimal;
|
||||
toSignificantDigits(significantDigits: number, rounding: Decimal.Rounding): Decimal;
|
||||
toSD(significantDigits?: number): Decimal;
|
||||
toSD(significantDigits: number, rounding: Decimal.Rounding): Decimal;
|
||||
|
||||
toString(): string;
|
||||
|
||||
truncated(): Decimal;
|
||||
trunc(): Decimal;
|
||||
|
||||
valueOf(): string;
|
||||
|
||||
static abs(n: Decimal.Value): Decimal;
|
||||
static acos(n: Decimal.Value): Decimal;
|
||||
static acosh(n: Decimal.Value): Decimal;
|
||||
static add(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static asin(n: Decimal.Value): Decimal;
|
||||
static asinh(n: Decimal.Value): Decimal;
|
||||
static atan(n: Decimal.Value): Decimal;
|
||||
static atanh(n: Decimal.Value): Decimal;
|
||||
static atan2(y: Decimal.Value, x: Decimal.Value): Decimal;
|
||||
static cbrt(n: Decimal.Value): Decimal;
|
||||
static ceil(n: Decimal.Value): Decimal;
|
||||
static clamp(n: Decimal.Value, min: Decimal.Value, max: Decimal.Value): Decimal;
|
||||
static clone(object?: Decimal.Config): Decimal.Constructor;
|
||||
static config(object: Decimal.Config): Decimal.Constructor;
|
||||
static cos(n: Decimal.Value): Decimal;
|
||||
static cosh(n: Decimal.Value): Decimal;
|
||||
static div(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static exp(n: Decimal.Value): Decimal;
|
||||
static floor(n: Decimal.Value): Decimal;
|
||||
static hypot(...n: Decimal.Value[]): Decimal;
|
||||
static isDecimal(object: any): object is Decimal;
|
||||
static ln(n: Decimal.Value): Decimal;
|
||||
static log(n: Decimal.Value, base?: Decimal.Value): Decimal;
|
||||
static log2(n: Decimal.Value): Decimal;
|
||||
static log10(n: Decimal.Value): Decimal;
|
||||
static max(...n: Decimal.Value[]): Decimal;
|
||||
static min(...n: Decimal.Value[]): Decimal;
|
||||
static mod(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static mul(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static noConflict(): Decimal.Constructor; // Browser only
|
||||
static pow(base: Decimal.Value, exponent: Decimal.Value): Decimal;
|
||||
static random(significantDigits?: number): Decimal;
|
||||
static round(n: Decimal.Value): Decimal;
|
||||
static set(object: Decimal.Config): Decimal.Constructor;
|
||||
static sign(n: Decimal.Value): number;
|
||||
static sin(n: Decimal.Value): Decimal;
|
||||
static sinh(n: Decimal.Value): Decimal;
|
||||
static sqrt(n: Decimal.Value): Decimal;
|
||||
static sub(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static sum(...n: Decimal.Value[]): Decimal;
|
||||
static tan(n: Decimal.Value): Decimal;
|
||||
static tanh(n: Decimal.Value): Decimal;
|
||||
static trunc(n: Decimal.Value): Decimal;
|
||||
|
||||
static readonly default?: Decimal.Constructor;
|
||||
static readonly Decimal?: Decimal.Constructor;
|
||||
|
||||
static readonly precision: number;
|
||||
static readonly rounding: Decimal.Rounding;
|
||||
static readonly toExpNeg: number;
|
||||
static readonly toExpPos: number;
|
||||
static readonly minE: number;
|
||||
static readonly maxE: number;
|
||||
static readonly crypto: boolean;
|
||||
static readonly modulo: Decimal.Modulo;
|
||||
|
||||
static readonly ROUND_UP: 0;
|
||||
static readonly ROUND_DOWN: 1;
|
||||
static readonly ROUND_CEIL: 2;
|
||||
static readonly ROUND_FLOOR: 3;
|
||||
static readonly ROUND_HALF_UP: 4;
|
||||
static readonly ROUND_HALF_DOWN: 5;
|
||||
static readonly ROUND_HALF_EVEN: 6;
|
||||
static readonly ROUND_HALF_CEIL: 7;
|
||||
static readonly ROUND_HALF_FLOOR: 8;
|
||||
static readonly EUCLID: 9;
|
||||
}
|
||||
|
||||
declare type Exact<A, W> = (A extends unknown ? (W extends A ? {
|
||||
[K in keyof A]: Exact<A[K], W[K]>;
|
||||
} : W) : never) | (A extends Narrowable ? A : never);
|
||||
|
||||
export declare function getRuntime(): GetRuntimeOutput;
|
||||
|
||||
declare type GetRuntimeOutput = {
|
||||
id: RuntimeName;
|
||||
prettyName: string;
|
||||
isEdge: boolean;
|
||||
};
|
||||
|
||||
declare class JsonNull extends NullTypesEnumValue {
|
||||
private readonly _brand_JsonNull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates more strict variant of an enum which, unlike regular enum,
|
||||
* throws on non-existing property access. This can be useful in following situations:
|
||||
* - we have an API, that accepts both `undefined` and `SomeEnumType` as an input
|
||||
* - enum values are generated dynamically from DMMF.
|
||||
*
|
||||
* In that case, if using normal enums and no compile-time typechecking, using non-existing property
|
||||
* will result in `undefined` value being used, which will be accepted. Using strict enum
|
||||
* in this case will help to have a runtime exception, telling you that you are probably doing something wrong.
|
||||
*
|
||||
* Note: if you need to check for existence of a value in the enum you can still use either
|
||||
* `in` operator or `hasOwnProperty` function.
|
||||
*
|
||||
* @param definition
|
||||
* @returns
|
||||
*/
|
||||
export declare function makeStrictEnum<T extends Record<PropertyKey, string | number>>(definition: T): T;
|
||||
|
||||
declare type Narrowable = string | number | bigint | boolean | [];
|
||||
|
||||
declare class NullTypesEnumValue extends ObjectEnumValue {
|
||||
_getNamespace(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for unique values of object-valued enums.
|
||||
*/
|
||||
declare abstract class ObjectEnumValue {
|
||||
constructor(arg?: symbol);
|
||||
abstract _getNamespace(): string;
|
||||
_getName(): string;
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
export declare const objectEnumValues: {
|
||||
classes: {
|
||||
DbNull: typeof DbNull;
|
||||
JsonNull: typeof JsonNull;
|
||||
AnyNull: typeof AnyNull;
|
||||
};
|
||||
instances: {
|
||||
DbNull: DbNull;
|
||||
JsonNull: JsonNull;
|
||||
AnyNull: AnyNull;
|
||||
};
|
||||
};
|
||||
|
||||
declare type Operation = 'findFirst' | 'findFirstOrThrow' | 'findUnique' | 'findUniqueOrThrow' | 'findMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'update' | 'updateMany' | 'updateManyAndReturn' | 'upsert' | 'delete' | 'deleteMany' | 'aggregate' | 'count' | 'groupBy' | '$queryRaw' | '$executeRaw' | '$queryRawUnsafe' | '$executeRawUnsafe' | 'findRaw' | 'aggregateRaw' | '$runCommandRaw';
|
||||
|
||||
declare namespace Public {
|
||||
export {
|
||||
validator
|
||||
}
|
||||
}
|
||||
export { Public }
|
||||
|
||||
declare type RuntimeName = 'workerd' | 'deno' | 'netlify' | 'node' | 'bun' | 'edge-light' | '';
|
||||
|
||||
declare function validator<V>(): <S>(select: Exact<S, V>) => S;
|
||||
|
||||
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation>(client: C, model: M, operation: O): <S>(select: Exact<S, Args<C[M], O>>) => S;
|
||||
|
||||
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation, P extends keyof Args<C[M], O>>(client: C, model: M, operation: O, prop: P): <S>(select: Exact<S, Args<C[M], O>[P]>) => S;
|
||||
|
||||
export { }
|
||||
13
src/generated/prisma/runtime/index-browser.js
Normal file
13
src/generated/prisma/runtime/index-browser.js
Normal file
File diff suppressed because one or more lines are too long
3604
src/generated/prisma/runtime/library.d.ts
vendored
Normal file
3604
src/generated/prisma/runtime/library.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
143
src/generated/prisma/runtime/library.js
Normal file
143
src/generated/prisma/runtime/library.js
Normal file
File diff suppressed because one or more lines are too long
80
src/generated/prisma/runtime/react-native.js
vendored
Normal file
80
src/generated/prisma/runtime/react-native.js
vendored
Normal file
File diff suppressed because one or more lines are too long
32
src/generated/prisma/runtime/wasm.js
Normal file
32
src/generated/prisma/runtime/wasm.js
Normal file
File diff suppressed because one or more lines are too long
63
src/generated/prisma/schema.prisma
Normal file
63
src/generated/prisma/schema.prisma
Normal file
@ -0,0 +1,63 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
|
||||
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "../src/generated/prisma"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "mysql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Project {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
repository String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deployments Deployment[]
|
||||
environments Environment[]
|
||||
}
|
||||
|
||||
model Deployment {
|
||||
id Int @id @default(autoincrement())
|
||||
projectId Int
|
||||
project Project @relation(fields: [projectId], references: [id])
|
||||
environmentId Int
|
||||
environment Environment @relation(fields: [environmentId], references: [id])
|
||||
status String @default("pending") // pending, success, failed
|
||||
commitHash String
|
||||
deployedAt DateTime @default(now())
|
||||
logs String? @db.Text
|
||||
}
|
||||
|
||||
model Environment {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
url String
|
||||
projectId Int
|
||||
project Project @relation(fields: [projectId], references: [id])
|
||||
deployments Deployment[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
email String @unique
|
||||
password String
|
||||
name String
|
||||
surname String
|
||||
active Boolean @default(true)
|
||||
role String @default("user") // user, admin, etc.
|
||||
lastLogin DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
resetToken String?
|
||||
resetTokenExpiry DateTime?
|
||||
}
|
||||
1
src/generated/prisma/wasm.d.ts
vendored
Normal file
1
src/generated/prisma/wasm.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export * from "./index"
|
||||
234
src/generated/prisma/wasm.js
Normal file
234
src/generated/prisma/wasm.js
Normal file
@ -0,0 +1,234 @@
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
|
||||
const {
|
||||
Decimal,
|
||||
objectEnumValues,
|
||||
makeStrictEnum,
|
||||
Public,
|
||||
getRuntime,
|
||||
skip
|
||||
} = require('./runtime/index-browser.js')
|
||||
|
||||
|
||||
const Prisma = {}
|
||||
|
||||
exports.Prisma = Prisma
|
||||
exports.$Enums = {}
|
||||
|
||||
/**
|
||||
* Prisma Client JS version: 6.6.0
|
||||
* Query Engine version: f676762280b54cd07c770017ed3711ddde35f37a
|
||||
*/
|
||||
Prisma.prismaVersion = {
|
||||
client: "6.6.0",
|
||||
engine: "f676762280b54cd07c770017ed3711ddde35f37a"
|
||||
}
|
||||
|
||||
Prisma.PrismaClientKnownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)};
|
||||
Prisma.PrismaClientUnknownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientRustPanicError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientInitializationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientValidationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.Decimal = Decimal
|
||||
|
||||
/**
|
||||
* Re-export of sql-template-tag
|
||||
*/
|
||||
Prisma.sql = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.empty = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.join = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.raw = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.validator = Public.validator
|
||||
|
||||
/**
|
||||
* Extensions
|
||||
*/
|
||||
Prisma.getExtensionContext = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.defineExtension = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
|
||||
/**
|
||||
* Shorthand utilities for JSON filtering
|
||||
*/
|
||||
Prisma.DbNull = objectEnumValues.instances.DbNull
|
||||
Prisma.JsonNull = objectEnumValues.instances.JsonNull
|
||||
Prisma.AnyNull = objectEnumValues.instances.AnyNull
|
||||
|
||||
Prisma.NullTypes = {
|
||||
DbNull: objectEnumValues.classes.DbNull,
|
||||
JsonNull: objectEnumValues.classes.JsonNull,
|
||||
AnyNull: objectEnumValues.classes.AnyNull
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Enums
|
||||
*/
|
||||
|
||||
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||
ReadUncommitted: 'ReadUncommitted',
|
||||
ReadCommitted: 'ReadCommitted',
|
||||
RepeatableRead: 'RepeatableRead',
|
||||
Serializable: 'Serializable'
|
||||
});
|
||||
|
||||
exports.Prisma.ProjectScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
repository: 'repository',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.DeploymentScalarFieldEnum = {
|
||||
id: 'id',
|
||||
projectId: 'projectId',
|
||||
environmentId: 'environmentId',
|
||||
status: 'status',
|
||||
commitHash: 'commitHash',
|
||||
deployedAt: 'deployedAt',
|
||||
logs: 'logs'
|
||||
};
|
||||
|
||||
exports.Prisma.EnvironmentScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
url: 'url',
|
||||
projectId: 'projectId',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.UserScalarFieldEnum = {
|
||||
id: 'id',
|
||||
email: 'email',
|
||||
password: 'password',
|
||||
name: 'name',
|
||||
surname: 'surname',
|
||||
active: 'active',
|
||||
role: 'role',
|
||||
lastLogin: 'lastLogin',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
resetToken: 'resetToken',
|
||||
resetTokenExpiry: 'resetTokenExpiry'
|
||||
};
|
||||
|
||||
exports.Prisma.SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
};
|
||||
|
||||
exports.Prisma.ProjectOrderByRelevanceFieldEnum = {
|
||||
name: 'name',
|
||||
repository: 'repository'
|
||||
};
|
||||
|
||||
exports.Prisma.NullsOrder = {
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
};
|
||||
|
||||
exports.Prisma.DeploymentOrderByRelevanceFieldEnum = {
|
||||
status: 'status',
|
||||
commitHash: 'commitHash',
|
||||
logs: 'logs'
|
||||
};
|
||||
|
||||
exports.Prisma.EnvironmentOrderByRelevanceFieldEnum = {
|
||||
name: 'name',
|
||||
url: 'url'
|
||||
};
|
||||
|
||||
exports.Prisma.UserOrderByRelevanceFieldEnum = {
|
||||
email: 'email',
|
||||
password: 'password',
|
||||
name: 'name',
|
||||
surname: 'surname',
|
||||
role: 'role',
|
||||
resetToken: 'resetToken'
|
||||
};
|
||||
|
||||
|
||||
exports.Prisma.ModelName = {
|
||||
Project: 'Project',
|
||||
Deployment: 'Deployment',
|
||||
Environment: 'Environment',
|
||||
User: 'User'
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a stub Prisma Client that will error at runtime if called.
|
||||
*/
|
||||
class PrismaClient {
|
||||
constructor() {
|
||||
return new Proxy(this, {
|
||||
get(target, prop) {
|
||||
let message
|
||||
const runtime = getRuntime()
|
||||
if (runtime.isEdge) {
|
||||
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
|
||||
- Use Prisma Accelerate: https://pris.ly/d/accelerate
|
||||
- Use Driver Adapters: https://pris.ly/d/driver-adapters
|
||||
`;
|
||||
} else {
|
||||
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
|
||||
}
|
||||
|
||||
message += `
|
||||
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
|
||||
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.PrismaClient = PrismaClient
|
||||
|
||||
Object.assign(exports, Prisma)
|
||||
@ -4,7 +4,6 @@ import { Link, useLocation } from "react-router";
|
||||
// Assume these icons are imported from an icon library
|
||||
import {
|
||||
BoxCubeIcon,
|
||||
CalenderIcon,
|
||||
ChevronDownIcon,
|
||||
GridIcon,
|
||||
HorizontaLDots,
|
||||
@ -30,57 +29,14 @@ const navItems: NavItem[] = [
|
||||
name: "Dashboard",
|
||||
path: "/",
|
||||
},
|
||||
{
|
||||
icon: <CalenderIcon />,
|
||||
name: "Calendar",
|
||||
path: "/calendar",
|
||||
},
|
||||
{
|
||||
icon: <UserCircleIcon />,
|
||||
name: "User Profile",
|
||||
path: "/profile",
|
||||
},
|
||||
{
|
||||
name: "Forms",
|
||||
icon: <ListIcon />,
|
||||
subItems: [{ name: "Form Elements", path: "/form-elements", pro: false }],
|
||||
},
|
||||
{
|
||||
name: "Tables",
|
||||
icon: <TableIcon />,
|
||||
subItems: [{ name: "Basic Tables", path: "/basic-tables", pro: false }],
|
||||
},
|
||||
{
|
||||
name: "Pages",
|
||||
icon: <PageIcon />,
|
||||
subItems: [
|
||||
{ name: "Blank Page", path: "/blank", pro: false },
|
||||
{ name: "404 Error", path: "/error-404", pro: false },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const othersItems: NavItem[] = [
|
||||
{
|
||||
icon: <PieChartIcon />,
|
||||
name: "Charts",
|
||||
subItems: [
|
||||
{ name: "Line Chart", path: "/line-chart", pro: false },
|
||||
{ name: "Bar Chart", path: "/bar-chart", pro: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: <BoxCubeIcon />,
|
||||
name: "UI Elements",
|
||||
subItems: [
|
||||
{ name: "Alerts", path: "/alerts", pro: false },
|
||||
{ name: "Avatar", path: "/avatars", pro: false },
|
||||
{ name: "Badge", path: "/badge", pro: false },
|
||||
{ name: "Buttons", path: "/buttons", pro: false },
|
||||
{ name: "Images", path: "/images", pro: false },
|
||||
{ name: "Videos", path: "/videos", pro: false },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const AppSidebar: React.FC = () => {
|
||||
|
||||
@ -4,7 +4,7 @@ import "./index.css";
|
||||
import "swiper/swiper-bundle.css";
|
||||
import "flatpickr/dist/flatpickr.css";
|
||||
import App from "./App.tsx";
|
||||
import { AppWrapper } from "./components/common/PageMeta.tsx";
|
||||
import { HeadProvider } from "./components/common/PageHead.tsx";
|
||||
import { ThemeProvider } from "./context/ThemeContext.tsx";
|
||||
import { AuthProvider } from "./context/AuthContext";
|
||||
|
||||
@ -12,9 +12,9 @@ createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<AuthProvider>
|
||||
<ThemeProvider>
|
||||
<AppWrapper>
|
||||
<HeadProvider>
|
||||
<App />
|
||||
</AppWrapper>
|
||||
</HeadProvider>
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
</StrictMode>,
|
||||
|
||||
@ -1,284 +0,0 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import FullCalendar from "@fullcalendar/react";
|
||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||
import interactionPlugin from "@fullcalendar/interaction";
|
||||
import { EventInput, DateSelectArg, EventClickArg } from "@fullcalendar/core";
|
||||
import { Modal } from "../components/ui/modal";
|
||||
import { useModal } from "../hooks/useModal";
|
||||
import PageMeta from "../components/common/PageMeta";
|
||||
|
||||
interface CalendarEvent extends EventInput {
|
||||
extendedProps: {
|
||||
calendar: string;
|
||||
};
|
||||
}
|
||||
|
||||
const Calendar: React.FC = () => {
|
||||
const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(
|
||||
null
|
||||
);
|
||||
const [eventTitle, setEventTitle] = useState("");
|
||||
const [eventStartDate, setEventStartDate] = useState("");
|
||||
const [eventEndDate, setEventEndDate] = useState("");
|
||||
const [eventLevel, setEventLevel] = useState("");
|
||||
const [events, setEvents] = useState<CalendarEvent[]>([]);
|
||||
const calendarRef = useRef<FullCalendar>(null);
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
|
||||
const calendarsEvents = {
|
||||
Danger: "danger",
|
||||
Success: "success",
|
||||
Primary: "primary",
|
||||
Warning: "warning",
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize with some events
|
||||
setEvents([
|
||||
{
|
||||
id: "1",
|
||||
title: "Event Conf.",
|
||||
start: new Date().toISOString().split("T")[0],
|
||||
extendedProps: { calendar: "Danger" },
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "Meeting",
|
||||
start: new Date(Date.now() + 86400000).toISOString().split("T")[0],
|
||||
extendedProps: { calendar: "Success" },
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
title: "Workshop",
|
||||
start: new Date(Date.now() + 172800000).toISOString().split("T")[0],
|
||||
end: new Date(Date.now() + 259200000).toISOString().split("T")[0],
|
||||
extendedProps: { calendar: "Primary" },
|
||||
},
|
||||
]);
|
||||
}, []);
|
||||
|
||||
const handleDateSelect = (selectInfo: DateSelectArg) => {
|
||||
resetModalFields();
|
||||
setEventStartDate(selectInfo.startStr);
|
||||
setEventEndDate(selectInfo.endStr || selectInfo.startStr);
|
||||
openModal();
|
||||
};
|
||||
|
||||
const handleEventClick = (clickInfo: EventClickArg) => {
|
||||
const event = clickInfo.event;
|
||||
setSelectedEvent(event as unknown as CalendarEvent);
|
||||
setEventTitle(event.title);
|
||||
setEventStartDate(event.start?.toISOString().split("T")[0] || "");
|
||||
setEventEndDate(event.end?.toISOString().split("T")[0] || "");
|
||||
setEventLevel(event.extendedProps.calendar);
|
||||
openModal();
|
||||
};
|
||||
|
||||
const handleAddOrUpdateEvent = () => {
|
||||
if (selectedEvent) {
|
||||
// Update existing event
|
||||
setEvents((prevEvents) =>
|
||||
prevEvents.map((event) =>
|
||||
event.id === selectedEvent.id
|
||||
? {
|
||||
...event,
|
||||
title: eventTitle,
|
||||
start: eventStartDate,
|
||||
end: eventEndDate,
|
||||
extendedProps: { calendar: eventLevel },
|
||||
}
|
||||
: event
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Add new event
|
||||
const newEvent: CalendarEvent = {
|
||||
id: Date.now().toString(),
|
||||
title: eventTitle,
|
||||
start: eventStartDate,
|
||||
end: eventEndDate,
|
||||
allDay: true,
|
||||
extendedProps: { calendar: eventLevel },
|
||||
};
|
||||
setEvents((prevEvents) => [...prevEvents, newEvent]);
|
||||
}
|
||||
closeModal();
|
||||
resetModalFields();
|
||||
};
|
||||
|
||||
const resetModalFields = () => {
|
||||
setEventTitle("");
|
||||
setEventStartDate("");
|
||||
setEventEndDate("");
|
||||
setEventLevel("");
|
||||
setSelectedEvent(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageMeta
|
||||
title="React.js Calendar Dashboard | TailAdmin - Next.js Admin Dashboard Template"
|
||||
description="This is React.js Calendar Dashboard page for TailAdmin - React.js Tailwind CSS Admin Dashboard Template"
|
||||
/>
|
||||
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div className="custom-calendar">
|
||||
<FullCalendar
|
||||
ref={calendarRef}
|
||||
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
||||
initialView="dayGridMonth"
|
||||
headerToolbar={{
|
||||
left: "prev,next addEventButton",
|
||||
center: "title",
|
||||
right: "dayGridMonth,timeGridWeek,timeGridDay",
|
||||
}}
|
||||
events={events}
|
||||
selectable={true}
|
||||
select={handleDateSelect}
|
||||
eventClick={handleEventClick}
|
||||
eventContent={renderEventContent}
|
||||
customButtons={{
|
||||
addEventButton: {
|
||||
text: "Add Event +",
|
||||
click: openModal,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={closeModal}
|
||||
className="max-w-[700px] p-6 lg:p-10"
|
||||
>
|
||||
<div className="flex flex-col px-2 overflow-y-auto custom-scrollbar">
|
||||
<div>
|
||||
<h5 className="mb-2 font-semibold text-gray-800 modal-title text-theme-xl dark:text-white/90 lg:text-2xl">
|
||||
{selectedEvent ? "Edit Event" : "Add Event"}
|
||||
</h5>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Plan your next big moment: schedule or edit an event to stay on
|
||||
track
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-8">
|
||||
<div>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Event Title
|
||||
</label>
|
||||
<input
|
||||
id="event-title"
|
||||
type="text"
|
||||
value={eventTitle}
|
||||
onChange={(e) => setEventTitle(e.target.value)}
|
||||
className="dark:bg-dark-900 h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<label className="block mb-4 text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Event Color
|
||||
</label>
|
||||
<div className="flex flex-wrap items-center gap-4 sm:gap-5">
|
||||
{Object.entries(calendarsEvents).map(([key, value]) => (
|
||||
<div key={key} className="n-chk">
|
||||
<div
|
||||
className={`form-check form-check-${value} form-check-inline`}
|
||||
>
|
||||
<label
|
||||
className="flex items-center text-sm text-gray-700 form-check-label dark:text-gray-400"
|
||||
htmlFor={`modal${key}`}
|
||||
>
|
||||
<span className="relative">
|
||||
<input
|
||||
className="sr-only form-check-input"
|
||||
type="radio"
|
||||
name="event-level"
|
||||
value={key}
|
||||
id={`modal${key}`}
|
||||
checked={eventLevel === key}
|
||||
onChange={() => setEventLevel(key)}
|
||||
/>
|
||||
<span className="flex items-center justify-center w-5 h-5 mr-2 border border-gray-300 rounded-full box dark:border-gray-700">
|
||||
<span
|
||||
className={`h-2 w-2 rounded-full bg-white ${
|
||||
eventLevel === key ? "block" : "hidden"
|
||||
}`}
|
||||
></span>
|
||||
</span>
|
||||
</span>
|
||||
{key}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Enter Start Date
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
id="event-start-date"
|
||||
type="date"
|
||||
value={eventStartDate}
|
||||
onChange={(e) => setEventStartDate(e.target.value)}
|
||||
className="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 pl-4 pr-11 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Enter End Date
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
id="event-end-date"
|
||||
type="date"
|
||||
value={eventEndDate}
|
||||
onChange={(e) => setEventEndDate(e.target.value)}
|
||||
className="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 pl-4 pr-11 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 mt-6 modal-footer sm:justify-end">
|
||||
<button
|
||||
onClick={closeModal}
|
||||
type="button"
|
||||
className="flex w-full justify-center rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] sm:w-auto"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAddOrUpdateEvent}
|
||||
type="button"
|
||||
className="btn btn-success btn-update-event flex w-full justify-center rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-600 sm:w-auto"
|
||||
>
|
||||
{selectedEvent ? "Update Changes" : "Add Event"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderEventContent = (eventInfo: any) => {
|
||||
const colorClass = `fc-bg-${eventInfo.event.extendedProps.calendar.toLowerCase()}`;
|
||||
return (
|
||||
<div
|
||||
className={`event-fc-color flex fc-event-main ${colorClass} p-1 rounded-sm`}
|
||||
>
|
||||
<div className="fc-daygrid-event-dot"></div>
|
||||
<div className="fc-event-time">{eventInfo.timeText}</div>
|
||||
<div className="fc-event-title">{eventInfo.event.title}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Calendar;
|
||||
104
src/services/authService.ts
Normal file
104
src/services/authService.ts
Normal file
@ -0,0 +1,104 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const crypto = require('crypto-js');
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
/**
|
||||
* Authentication service for the backend API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Response structure for login attempts
|
||||
*/
|
||||
interface LoginResponse {
|
||||
success: boolean;
|
||||
token?: string;
|
||||
error?: string;
|
||||
user?: {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
role: string;
|
||||
};
|
||||
}
|
||||
|
||||
// Generate a secure token
|
||||
const generateToken = (length = 64) => {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let token = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
token += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
return token;
|
||||
};
|
||||
|
||||
/**
|
||||
* Authenticate user against the database
|
||||
*/
|
||||
async function authenticateUser(email, password) {
|
||||
try {
|
||||
// Hash the password using the same algorithm as the client
|
||||
const hashedPassword = crypto.SHA256(password).toString();
|
||||
|
||||
// Find the user with matching email
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email }
|
||||
});
|
||||
|
||||
// User not found
|
||||
if (!user) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'User not found'
|
||||
};
|
||||
}
|
||||
|
||||
// Check if user is active
|
||||
if (!user.active) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Account is inactive'
|
||||
};
|
||||
}
|
||||
|
||||
// Verify password
|
||||
if (user.password !== hashedPassword) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid credentials'
|
||||
};
|
||||
}
|
||||
|
||||
// Generate authentication token
|
||||
const token = generateToken();
|
||||
|
||||
// Update last login timestamp
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { lastLogin: new Date() }
|
||||
});
|
||||
|
||||
// Return successful login response
|
||||
return {
|
||||
success: true,
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
name: `${user.name} ${user.surname}`,
|
||||
email: user.email,
|
||||
role: user.role
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Authentication error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'An error occurred during authentication'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export using CommonJS syntax
|
||||
module.exports = {
|
||||
authenticateUser
|
||||
};
|
||||
68
src/services/dbService.ts
Normal file
68
src/services/dbService.ts
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Database service for frontend
|
||||
*
|
||||
* This service provides an interface for frontend code to make database
|
||||
* operations via API calls instead of directly using Prisma in the browser
|
||||
*/
|
||||
|
||||
import { API_BASE_URL } from '../config/apiConfig';
|
||||
|
||||
// API endpoints
|
||||
const API_ENDPOINTS = {
|
||||
LOGIN: '/api/login',
|
||||
USERS: '/api/users',
|
||||
};
|
||||
|
||||
/**
|
||||
* User authentication service
|
||||
*/
|
||||
export const authApi = {
|
||||
/**
|
||||
* Authenticate a user with email and password
|
||||
*/
|
||||
login: async (email: string, hashedPassword: string) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}${API_ENDPOINTS.LOGIN}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
body: JSON.stringify({ email, password: hashedPassword }),
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to connect to authentication service'
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* User management service
|
||||
*/
|
||||
export const usersApi = {
|
||||
/**
|
||||
* Get user by email
|
||||
*/
|
||||
getUserByEmail: async (email: string, token: string) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}${API_ENDPOINTS.USERS}?email=${encodeURIComponent(email)}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Get user error:', error);
|
||||
return { success: false, error: 'Failed to fetch user data' };
|
||||
}
|
||||
},
|
||||
};
|
||||
117
src/services/localApi.ts
Normal file
117
src/services/localApi.ts
Normal file
@ -0,0 +1,117 @@
|
||||
// Local API service to handle authentication
|
||||
import { API_BASE_URL } from '../config/apiConfig';
|
||||
|
||||
// Generate a random token as fallback if API fails
|
||||
const generateToken = (length = 32) => {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let token = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
token += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
return token;
|
||||
};
|
||||
|
||||
interface LoginResponse {
|
||||
success: boolean;
|
||||
token?: string;
|
||||
error?: string;
|
||||
user?: {
|
||||
id?: number;
|
||||
name: string;
|
||||
email: string;
|
||||
role: string;
|
||||
};
|
||||
}
|
||||
|
||||
// Demo users for fallback if database connection fails
|
||||
const FALLBACK_USERS = [
|
||||
{
|
||||
email: 'admin@example.com',
|
||||
// This is a SHA-256 hash of 'admin123'
|
||||
passwordHash: '240be518fabd2724ddb6f04eeb1da5967448d7e831c08c8fa822809f74c720a9',
|
||||
name: 'Admin User',
|
||||
role: 'admin',
|
||||
},
|
||||
{
|
||||
email: 'user@example.com',
|
||||
// This is a SHA-256 hash of 'user123'
|
||||
passwordHash: 'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3',
|
||||
name: 'Regular User',
|
||||
role: 'user',
|
||||
}
|
||||
];
|
||||
|
||||
// Login function that uses MySQL database via API
|
||||
export const localLogin = async (email: string, hashedPassword: string): Promise<LoginResponse> => {
|
||||
try {
|
||||
console.log('Attempting to login with database:', email);
|
||||
|
||||
// Call the login API endpoint
|
||||
const response = await fetch(`${API_BASE_URL}/api/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
body: JSON.stringify({ email, password: hashedPassword }),
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
// Parse the API response
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result.success) {
|
||||
console.log('Database login successful for:', email);
|
||||
return result;
|
||||
}
|
||||
|
||||
console.warn('Database login failed, error:', result.error);
|
||||
console.warn('Falling back to demo users...');
|
||||
|
||||
// If API call fails, fall back to demo users
|
||||
return fallbackLogin(email, hashedPassword);
|
||||
} catch (error) {
|
||||
console.error('API login error, falling back to demo users:', error);
|
||||
return fallbackLogin(email, hashedPassword);
|
||||
}
|
||||
};
|
||||
|
||||
// Fallback login function using demo users
|
||||
const fallbackLogin = async (email: string, hashedPassword: string): Promise<LoginResponse> => {
|
||||
// Simulate network delay
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
|
||||
// Find user with matching email
|
||||
const user = FALLBACK_USERS.find(u => u.email === email);
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'User not found'
|
||||
};
|
||||
}
|
||||
|
||||
// Check password hash
|
||||
if (user.passwordHash !== hashedPassword) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid credentials'
|
||||
};
|
||||
}
|
||||
|
||||
// Generate authentication token
|
||||
const token = generateToken();
|
||||
|
||||
console.log('Fallback login successful for demo user:', user.email);
|
||||
|
||||
// Return successful login response
|
||||
return {
|
||||
success: true,
|
||||
token,
|
||||
user: {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
role: user.role
|
||||
}
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user