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"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fullcalendar/core": "^6.1.17",
|
"@prisma/client": "^6.6.0",
|
||||||
"@fullcalendar/daygrid": "^6.1.17",
|
"@types/bcrypt": "^5.0.2",
|
||||||
"@fullcalendar/interaction": "^6.1.17",
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@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",
|
|
||||||
"apexcharts": "^4.5.0",
|
"apexcharts": "^4.5.0",
|
||||||
"axios": "^1.8.4",
|
"axios": "^1.8.4",
|
||||||
|
"bcrypt": "^5.1.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"flatpickr": "^4.6.13",
|
"flatpickr": "^4.6.13",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-apexcharts": "^1.7.0",
|
"react-apexcharts": "^1.7.0",
|
||||||
@ -28,7 +25,6 @@
|
|||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-dropzone": "^14.3.8",
|
"react-dropzone": "^14.3.8",
|
||||||
"react-helmet-async": "^2.0.5",
|
|
||||||
"react-router-dom": "^7.5.0",
|
"react-router-dom": "^7.5.0",
|
||||||
"swiper": "^11.2.6",
|
"swiper": "^11.2.6",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
@ -44,6 +40,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^5.1.0",
|
"eslint-plugin-react-hooks": "^5.1.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
|
"prisma": "^6.6.0",
|
||||||
"tailwindcss": "^4.1.3",
|
"tailwindcss": "^4.1.3",
|
||||||
"typescript": "~5.7.2",
|
"typescript": "~5.7.2",
|
||||||
"typescript-eslint": "^8.24.1",
|
"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 Buttons from "./pages/UiElements/Buttons";
|
||||||
import LineChart from "./pages/Charts/LineChart";
|
import LineChart from "./pages/Charts/LineChart";
|
||||||
import BarChart from "./pages/Charts/BarChart";
|
import BarChart from "./pages/Charts/BarChart";
|
||||||
import Calendar from "./pages/Calendar";
|
|
||||||
import BasicTables from "./pages/Tables/BasicTables";
|
import BasicTables from "./pages/Tables/BasicTables";
|
||||||
import FormElements from "./pages/Forms/FormElements";
|
import FormElements from "./pages/Forms/FormElements";
|
||||||
import Blank from "./pages/Blank";
|
import Blank from "./pages/Blank";
|
||||||
@ -31,7 +30,6 @@ export default function App() {
|
|||||||
|
|
||||||
{/* Others Page */}
|
{/* Others Page */}
|
||||||
<Route path="/profile" element={<UserProfiles />} />
|
<Route path="/profile" element={<UserProfiles />} />
|
||||||
<Route path="/calendar" element={<Calendar />} />
|
|
||||||
<Route path="/blank" element={<Blank />} />
|
<Route path="/blank" element={<Blank />} />
|
||||||
|
|
||||||
{/* Forms */}
|
{/* Forms */}
|
||||||
@ -46,7 +44,6 @@ export default function App() {
|
|||||||
<Route path="/badge" element={<Badges />} />
|
<Route path="/badge" element={<Badges />} />
|
||||||
<Route path="/buttons" element={<Buttons />} />
|
<Route path="/buttons" element={<Buttons />} />
|
||||||
<Route path="/images" element={<Images />} />
|
<Route path="/images" element={<Images />} />
|
||||||
<Route path="/videos" element={<Videos />} />
|
|
||||||
|
|
||||||
{/* Charts */}
|
{/* Charts */}
|
||||||
<Route path="/line-chart" element={<LineChart />} />
|
<Route path="/line-chart" element={<LineChart />} />
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
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 Login: React.FC = () => {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
@ -15,35 +19,57 @@ const Login: React.FC = () => {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Send credentials as JSON
|
// Create a SHA-256 hash of the password
|
||||||
const response = await fetch(`${API_BASE_URL}/api/login.php`, {
|
const hashedPassword = crypto.SHA256(password).toString();
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (response.ok) {
|
// Choose between local and remote API based on configuration
|
||||||
// If response is successful, we should have a token
|
if (isLocalMode()) {
|
||||||
if (data.token) {
|
// Use local authentication
|
||||||
login(data.token);
|
const result = await localLogin(email, hashedPassword);
|
||||||
|
|
||||||
|
if (result.success && result.token) {
|
||||||
|
login(result.token);
|
||||||
} else {
|
} else {
|
||||||
setError('Invalid server response');
|
setError(result.error || 'Authentication failed');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Handle error responses
|
// Use remote authentication (original implementation)
|
||||||
setError(data.error || 'Authentication failed');
|
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) {
|
} catch (err) {
|
||||||
setError('Failed to connect to the server. Please try again.');
|
setError('Failed to connect to the server. Please try again.');
|
||||||
console.error('Login error:', err);
|
console.error('Login error:', err);
|
||||||
} finally {
|
} finally {
|
||||||
|
// Clear password from memory for security
|
||||||
|
setPassword('');
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -90,6 +116,13 @@ const Login: React.FC = () => {
|
|||||||
>
|
>
|
||||||
{isLoading ? 'Logging in...' : 'Login'}
|
{isLoading ? 'Logging in...' : 'Login'}
|
||||||
</button>
|
</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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</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 = ({
|
const PageMeta = ({
|
||||||
title,
|
title,
|
||||||
@ -7,14 +7,12 @@ const PageMeta = ({
|
|||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
}) => (
|
}) => (
|
||||||
<Helmet>
|
<PageHead title={title} description={description} />
|
||||||
<title>{title}</title>
|
|
||||||
<meta name="description" content={description} />
|
|
||||||
</Helmet>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// This is just for backward compatibility
|
||||||
export const AppWrapper = ({ children }: { children: React.ReactNode }) => (
|
export const AppWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||||
<HelmetProvider>{children}</HelmetProvider>
|
<>{children}</>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default PageMeta;
|
export default PageMeta;
|
||||||
|
|||||||
@ -1,2 +1,9 @@
|
|||||||
// Base URL configuration for API endpoints
|
// Base URL configuration for API endpoints
|
||||||
export const API_BASE_URL = 'https://deploy.projekti.info';
|
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
|
// Assume these icons are imported from an icon library
|
||||||
import {
|
import {
|
||||||
BoxCubeIcon,
|
BoxCubeIcon,
|
||||||
CalenderIcon,
|
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
GridIcon,
|
GridIcon,
|
||||||
HorizontaLDots,
|
HorizontaLDots,
|
||||||
@ -30,57 +29,14 @@ const navItems: NavItem[] = [
|
|||||||
name: "Dashboard",
|
name: "Dashboard",
|
||||||
path: "/",
|
path: "/",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
icon: <CalenderIcon />,
|
|
||||||
name: "Calendar",
|
|
||||||
path: "/calendar",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: <UserCircleIcon />,
|
icon: <UserCircleIcon />,
|
||||||
name: "User Profile",
|
name: "User Profile",
|
||||||
path: "/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[] = [
|
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 = () => {
|
const AppSidebar: React.FC = () => {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import "./index.css";
|
|||||||
import "swiper/swiper-bundle.css";
|
import "swiper/swiper-bundle.css";
|
||||||
import "flatpickr/dist/flatpickr.css";
|
import "flatpickr/dist/flatpickr.css";
|
||||||
import App from "./App.tsx";
|
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 { ThemeProvider } from "./context/ThemeContext.tsx";
|
||||||
import { AuthProvider } from "./context/AuthContext";
|
import { AuthProvider } from "./context/AuthContext";
|
||||||
|
|
||||||
@ -12,9 +12,9 @@ createRoot(document.getElementById("root")!).render(
|
|||||||
<StrictMode>
|
<StrictMode>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<AppWrapper>
|
<HeadProvider>
|
||||||
<App />
|
<App />
|
||||||
</AppWrapper>
|
</HeadProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</StrictMode>,
|
</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