mirror of
https://github.com/donavon04/DocuCenter.git
synced 2025-01-18 09:40:56 -07:00
Made a bunch of changes / fixed eevrything
This commit is contained in:
parent
2776f339c6
commit
8af208a8f5
@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
const express = require('express');
|
||||
|
||||
const msal = require('@azure/msal-node');
|
||||
|
||||
/**
|
||||
* If you have encrypted your private key with a *pass phrase* as recommended,
|
||||
* you'll need to decrypt it before passing it to msal-node for initialization.
|
||||
*/
|
||||
// Secrets should never be hardcoded. The dotenv npm package can be used to store secrets or certificates
|
||||
// in a .env file (located in project's root directory) that should be included in .gitignore to prevent
|
||||
// accidental uploads of the secrets.
|
||||
|
||||
// Certificates can also be read-in from files via NodeJS's fs module. However, they should never be
|
||||
// stored in the project's directory. Production apps should fetch certificates from
|
||||
// Azure KeyVault (https://azure.microsoft.com/products/key-vault), or other secure key vaults.
|
||||
|
||||
// Please see "Certificates and Secrets" (https://learn.microsoft.com/azure/active-directory/develop/security-best-practices-for-app-registration#certificates-and-secrets)
|
||||
// for more information.
|
||||
const privateKeySource = fs.readFileSync('../certs/example.key');
|
||||
|
||||
const privateKeyObject = crypto.createPrivateKey({
|
||||
key: privateKeySource,
|
||||
passphrase: "2255", // enter your certificate passphrase here
|
||||
format: 'pem'
|
||||
});
|
||||
|
||||
const privateKey = privateKeyObject.export({
|
||||
format: 'pem',
|
||||
type: 'pkcs8'
|
||||
});
|
||||
|
||||
// Before running the sample, you will need to replace the values in the config
|
||||
const config = {
|
||||
auth: {
|
||||
clientId: "3cdfac60-e7fb-4648-89d3-67966c497d35", //Client ID
|
||||
authority: "https://login.microsoftonline.com/538b9b1c-23fa-4102-b36e-a4d83fc9c4c1", //Tenant ID
|
||||
clientCertificate: {
|
||||
thumbprint: 'DD79B973F2D634840948970C712907DF4423C982', // can be obtained when uploading certificate to Azure AD
|
||||
privateKey: privateKey,
|
||||
}
|
||||
},
|
||||
system: {
|
||||
loggerOptions: {
|
||||
loggerCallback(loglevel, message, containsPii) {
|
||||
console.log(message);
|
||||
},
|
||||
piiLoggingEnabled: false,
|
||||
logLevel: msal.LogLevel.Verbose,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create msal application object
|
||||
const cca = new msal.ConfidentialClientApplication(config);
|
||||
|
||||
// Create Express app
|
||||
const app = express();
|
||||
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
const authCodeUrlParameters = {
|
||||
scopes: ["user.read"],
|
||||
redirectUri: "http://localhost:3000/redirect",
|
||||
responseMode: 'form_post',
|
||||
};
|
||||
|
||||
// get url to sign user in and consent to scopes needed for application
|
||||
cca.getAuthCodeUrl(authCodeUrlParameters).then((response) => {
|
||||
console.log(response);
|
||||
res.redirect(response);
|
||||
}).catch((error) => console.log(JSON.stringify(error)));
|
||||
});
|
||||
|
||||
app.post('/redirect', (req, res) => {
|
||||
const tokenRequest = {
|
||||
code: req.body.code,
|
||||
scopes: ["user.read"],
|
||||
redirectUri: "http://localhost:3000/redirect",
|
||||
};
|
||||
|
||||
cca.acquireTokenByCode(tokenRequest).then((response) => {
|
||||
console.log("\nResponse: \n:", response);
|
||||
res.status(200).send('Congratulations! You have signed in successfully');
|
||||
}).catch((error) => {
|
||||
console.log(error);
|
||||
res.status(500).send(error);
|
||||
});
|
||||
});
|
||||
|
||||
const SERVER_PORT = process.env.PORT || 3000;
|
||||
|
||||
app.listen(SERVER_PORT, () => {
|
||||
console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`)
|
||||
});
|
@ -1,64 +0,0 @@
|
||||
const express = require('express');
|
||||
const session = require('express-session');
|
||||
const passport = require('passport');
|
||||
const { OIDCStrategy } = require('passport-azure-ad');
|
||||
|
||||
const app = express();
|
||||
|
||||
// Session setup
|
||||
app.use(
|
||||
session({
|
||||
secret: 'your-secret',
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
})
|
||||
);
|
||||
|
||||
// Azure AD OIDC Strategy
|
||||
passport.use(
|
||||
new OIDCStrategy(
|
||||
{
|
||||
identityMetadata: `https://login.microsoftonline.com/538b9b1c-23fa-4102-b36e-a4d83fc9c4c1/v2.0/.well-known/openid-configuration`,
|
||||
clientID: '3cdfac60-e7fb-4648-89d3-67966c497d35',
|
||||
responseType: 'code',
|
||||
responseMode: 'query',
|
||||
redirectUrl: 'http://localhost:3000/auth/callback',
|
||||
clientSecret: '5Gi8Q~_pmDtvN3.Jwqt85kiI.uiyAAC7Z.4iFayY',
|
||||
allowHttpForRedirectUrl: true,
|
||||
},
|
||||
(issuer, sub, profile, accessToken, refreshToken, done) => {
|
||||
// Save the user profile and tokens
|
||||
return done(null, { profile, accessToken, refreshToken });
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Passport serialization
|
||||
passport.serializeUser((user, done) => done(null, user));
|
||||
passport.deserializeUser((user, done) => done(null, user));
|
||||
|
||||
// Initialize Passport
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
// Authentication routes
|
||||
app.get('/auth', passport.authenticate('azuread-openidconnect'));
|
||||
|
||||
app.get(
|
||||
'/auth/callback',
|
||||
passport.authenticate('azuread-openidconnect', { failureRedirect: '/' }),
|
||||
(req, res) => {
|
||||
res.send("Success");
|
||||
}
|
||||
);
|
||||
|
||||
// Logout route
|
||||
app.get('/logout', (req, res) => {
|
||||
req.logout(() => {
|
||||
res.redirect('/');
|
||||
});
|
||||
});
|
||||
|
||||
// Start server
|
||||
const port = process.env.PORT || 3000;
|
||||
app.listen(port, () => console.log(`Server running on http://localhost:${port}`));
|
@ -35,7 +35,31 @@ exports.login = async (req, res) => {
|
||||
}
|
||||
|
||||
// Set the token as an HttpOnly cookie
|
||||
res.cookie('authToken', token, {
|
||||
res.cookie('token', token, {
|
||||
httpOnly: true, // Cannot be accessed by JavaScript
|
||||
secure: process.env.NODE_ENV === 'production', // Use Secure flag only in production (requires HTTPS)
|
||||
sameSite: 'Strict', // Prevents CSRF attacks
|
||||
maxAge: 3600000, // 1 hour
|
||||
});
|
||||
|
||||
// Set the token as an HttpOnly cookie
|
||||
res.cookie('name', user.name, {
|
||||
httpOnly: true, // Cannot be accessed by JavaScript
|
||||
secure: process.env.NODE_ENV === 'production', // Use Secure flag only in production (requires HTTPS)
|
||||
sameSite: 'Strict', // Prevents CSRF attacks
|
||||
maxAge: 3600000, // 1 hour
|
||||
});
|
||||
|
||||
// Set the token as an HttpOnly cookie
|
||||
res.cookie('email', user.email, {
|
||||
httpOnly: true, // Cannot be accessed by JavaScript
|
||||
secure: process.env.NODE_ENV === 'production', // Use Secure flag only in production (requires HTTPS)
|
||||
sameSite: 'Strict', // Prevents CSRF attacks
|
||||
maxAge: 3600000, // 1 hour
|
||||
});
|
||||
|
||||
// Set the token as an HttpOnly cookie
|
||||
res.cookie('role', user.role, {
|
||||
httpOnly: true, // Cannot be accessed by JavaScript
|
||||
secure: process.env.NODE_ENV === 'production', // Use Secure flag only in production (requires HTTPS)
|
||||
sameSite: 'Strict', // Prevents CSRF attacks
|
||||
|
@ -30,6 +30,47 @@ exports.getDocumentById = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
exports.searchDocuments = async (req, res) => {
|
||||
try {
|
||||
const { query } = req.query; // Extract the search query from the query string
|
||||
|
||||
if (!query) {
|
||||
return res.status(400).json({ message: "Search query is required." });
|
||||
}
|
||||
|
||||
// Perform the search
|
||||
const documents = await Document.find({
|
||||
$or: [
|
||||
{ title: { $regex: query, $options: "i" } }, // Case-insensitive match in title
|
||||
{ body: { $regex: query, $options: "i" } }, // Case-insensitive match in body
|
||||
{ tags: { $regex: query, $options: "i" } }, // Case-insensitive match in tags array
|
||||
],
|
||||
});
|
||||
|
||||
// Highlight the query in the fields
|
||||
const highlightedDocuments = documents.map((doc) => {
|
||||
const highlightMatch = (text) =>
|
||||
text.replace(
|
||||
new RegExp(`(${query})`, "gi"), // Match the query case-insensitively
|
||||
'<span style="background: yellow;">$1</span>'
|
||||
);
|
||||
|
||||
return {
|
||||
...doc._doc, // Convert Mongoose document to plain object
|
||||
title: highlightMatch(doc.title || ""),
|
||||
body: highlightMatch(doc.body || ""),
|
||||
tags: doc.tags.map((tag) => highlightMatch(tag)), // Highlight each tag
|
||||
};
|
||||
});
|
||||
|
||||
res.json(highlightedDocuments); // Return the matching and highlighted documents
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
exports.updateDocumentById = async (req, res) => {
|
||||
try {
|
||||
const documentId = req.params.id;
|
||||
|
@ -13,7 +13,7 @@ const DocumentSchema = new mongoose.Schema(
|
||||
visibility: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "Public",
|
||||
default: "public",
|
||||
},
|
||||
tags: {
|
||||
type: [String], //Array of strings
|
||||
|
@ -21,7 +21,7 @@ const UserSchema = new mongoose.Schema(
|
||||
role: {
|
||||
type: String,
|
||||
required: false, // Optional for OAuth users
|
||||
default: "User", //User = Read, Write Permissions | Admin = Read, Write, Delete Permissions
|
||||
default: "user", //User = Read, Write Permissions | Admin = Read, Write, Delete Permissions
|
||||
},
|
||||
oauthProviders: [
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ const router = express.Router();
|
||||
const documentController = require('../controllers/documentController');
|
||||
|
||||
router.post('/', documentController.createDocument); //Create
|
||||
router.get('/search', documentController.searchDocuments); //Get a document by id
|
||||
router.get('/', documentController.getDocument); //Get all documents
|
||||
router.get('/:id', documentController.getDocumentById); //Get a document by id
|
||||
router.put('/:id', authenticateToken, documentController.updateDocumentById); //Update a document by id
|
||||
|
@ -1 +1 @@
|
||||
BACKEND_URI='http://localhost:3000'
|
||||
PUBLIC_BASE_URL='http://localhost:3000'
|
20
frontend/src/hooks.server.ts
Normal file
20
frontend/src/hooks.server.ts
Normal file
@ -0,0 +1,20 @@
|
||||
export async function handle({ event, resolve }) {
|
||||
// this cookie would be set inside a login route
|
||||
const token = event.cookies.get('token');
|
||||
const name = event.cookies.get('name');
|
||||
const email = event.cookies.get('email');
|
||||
const role = event.cookies.get('role');
|
||||
|
||||
// you can get the user data from a database
|
||||
// const user = await getUser(session)
|
||||
|
||||
// this is passed to `event` inside server `load` functions
|
||||
// and passed to handlers inside `+page.ts`
|
||||
event.locals.user = {
|
||||
token: token,
|
||||
name: name,
|
||||
email: email,
|
||||
role: role
|
||||
}
|
||||
return resolve(event)
|
||||
}
|
@ -1,42 +1,3 @@
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||
|
||||
export const load: LayoutServerLoad = async ({ cookies, fetch }) => {
|
||||
let isAuthenticated = false;
|
||||
let user = null;
|
||||
let errorMessage = '';
|
||||
|
||||
// Retrieve the auth token from cookies
|
||||
const token = cookies.get('authToken');
|
||||
|
||||
if (!token) {
|
||||
errorMessage = 'No auth token found. Please log in.';
|
||||
return { isAuthenticated, user, errorMessage };
|
||||
}
|
||||
|
||||
try {
|
||||
// Make a server-side fetch request to validate the token
|
||||
const response = await fetch(`${PUBLIC_BASE_URL}/auth/status`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
isAuthenticated = data.authenticated;
|
||||
user = data.user;
|
||||
} else {
|
||||
errorMessage = 'Authentication failed. Please log in again.';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking authentication:', error);
|
||||
errorMessage = 'An error occurred while checking authentication.';
|
||||
}
|
||||
|
||||
// Return data to the layout
|
||||
return { isAuthenticated, user, errorMessage };
|
||||
};
|
||||
|
||||
export async function load({ locals }) {
|
||||
return { user: locals.user }
|
||||
}
|
||||
|
@ -1,32 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||
import '../app.postcss';
|
||||
import { page } from "$app/stores";
|
||||
import { isAuthenticated } from '$lib/stores/auth';
|
||||
|
||||
let dropDown = false; // For the dropdown menu (login menu)
|
||||
|
||||
export let data: {
|
||||
isAuthenticated: boolean;
|
||||
errorMessage: string;
|
||||
function getInitials(name) {
|
||||
return name
|
||||
.split(" ") // Split the name into words
|
||||
.slice(0, 2) // Take the first two words
|
||||
.map(word => word.charAt(0).toUpperCase()) // Get the first letter of each word and capitalize
|
||||
.join(""); // Join them into a string
|
||||
}
|
||||
|
||||
// Update the store values when this layout is loaded
|
||||
$: isAuthenticated.set(data.isAuthenticated); //This is a store (MAGIC)
|
||||
|
||||
const handleSignOut = async () => {
|
||||
const response = await fetch('/signout', {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
data.isAuthenticated = false;
|
||||
} else {
|
||||
console.error('Failed to sign out.');
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<header class="bg-blue-700 flex flex-row p-4 place-content-between items-center font-roboto shadow-xl">
|
||||
@ -35,17 +20,17 @@
|
||||
</a>
|
||||
|
||||
<div class="relative dropdown-container">
|
||||
<button class="rounded-3xl bg-blue-500 p-2 flex flex-row justify-center items-center p-1 hover:bg-blue-400"
|
||||
<button class="rounded-3xl bg-blue-500 flex flex-row justify-center items-center p-1 hover:bg-blue-400"
|
||||
on:click={() => dropDown = !dropDown}>
|
||||
{#if data.isAuthenticated}
|
||||
<img
|
||||
src="https://www.citypng.com/public/uploads/preview/hd-mcdonalds-red-round-circular-circle-logo-icon-png-image-7017516947898359qtpcakiqi.png"
|
||||
class="avatar rounded-full size-8"
|
||||
alt="User Avatar"
|
||||
/>
|
||||
{#if $page.data.user.name}
|
||||
<div class="rounded-full text-white font-bold bg-blue-400 p-2">
|
||||
{getInitials($page.data.user.name)}
|
||||
</div>
|
||||
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
|
||||
{:else}
|
||||
<div class="rounded-full text-white font-bold bg-blue-400 p-2 px-3">
|
||||
<i class="fa-solid fa-right-to-bracket" style="color: #FFFFFF;"></i>
|
||||
</div>
|
||||
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
|
||||
{/if}
|
||||
</button>
|
||||
@ -53,17 +38,16 @@
|
||||
{#if dropDown}
|
||||
<!-- Dropdown menu -->
|
||||
<div class="absolute right-0 mt-2 bg-white shadow-lg rounded-md p-4 z-50 text-nowrap">
|
||||
{#if data.isAuthenticated}
|
||||
{#if $page.data.user.name}
|
||||
<span class="signedInText block mb-2 text-sm">
|
||||
<small>Signed in</small><br/>
|
||||
<small>Signed in as</small><br/>
|
||||
<b>{$page.data.user.name}</b>
|
||||
</span>
|
||||
<button
|
||||
class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full"
|
||||
on:click={() => {dropDown = false; handleSignOut();}}>
|
||||
Sign Out
|
||||
</button>
|
||||
|
||||
<form action="/logout" method="POST">
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full">Sign Out</button>
|
||||
</form>
|
||||
{:else}
|
||||
<span class="notSignedInText block mb-2">You are not signed in</span>
|
||||
<a href="/login" on:click={() => {dropDown = false;}}>
|
||||
<button
|
||||
class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full"
|
||||
|
@ -1,59 +0,0 @@
|
||||
<script lang="ts">
|
||||
import '../app.postcss';
|
||||
import { signIn, signOut } from "@auth/sveltekit/client"
|
||||
import { page } from "$app/stores"
|
||||
|
||||
let dropDown = false;
|
||||
|
||||
</script>
|
||||
|
||||
<header class="bg-blue-700 flex flex-row p-4 place-content-between items-center font-roboto shadow-xl">
|
||||
<a href="/home">
|
||||
<img class="max-w-36 mr-5 ml-4 aspect-auto" src="/branding/mpe_logo.png" alt="MPE Logo">
|
||||
</a>
|
||||
|
||||
<div class="relative dropdown-container">
|
||||
<button class="rounded-3xl bg-blue-500 p-2 flex flex-row justify-center items-center p-1 hover:bg-blue-400"
|
||||
on:click={() => dropDown = !dropDown}>
|
||||
{#if $page.data.session}
|
||||
{#if $page.data.session.user?.image}
|
||||
<img
|
||||
src={$page.data.session.user.image}
|
||||
class="avatar rounded-full size-8"
|
||||
alt="User Avatar"
|
||||
/>
|
||||
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
|
||||
{/if}
|
||||
{:else}
|
||||
<i class="fa-solid fa-right-to-bracket" style="color: #FFFFFF;"></i>
|
||||
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
{#if dropDown}
|
||||
<!-- Dropdown menu -->
|
||||
<div class="absolute right-0 mt-2 bg-white shadow-lg rounded-md p-4 z-50 text-nowrap">
|
||||
{#if $page.data.session}
|
||||
<span class="signedInText block mb-2 text-sm">
|
||||
<small>Signed in as</small><br/>
|
||||
<strong>{$page.data.session.user?.name ?? "User"}</strong>
|
||||
</span>
|
||||
<button
|
||||
class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full"
|
||||
on:click={() => { signOut(); dropDown = false;}}>
|
||||
Sign Out
|
||||
</button>
|
||||
{:else}
|
||||
<span class="notSignedInText block mb-2">You are not signed in</span>
|
||||
<button
|
||||
class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full"
|
||||
on:click={() => { signIn('microsoft-entra-id'); dropDown = false;}}>
|
||||
Sign In
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<slot />
|
@ -13,9 +13,9 @@
|
||||
let title = "";
|
||||
|
||||
async function save() {
|
||||
const url = PUBLIC_BASE_URL+"/document";
|
||||
|
||||
let created_by = $page.data.session.user?.name ?? "User";
|
||||
const url = `${PUBLIC_BASE_URL}/api/document`;
|
||||
const token = $page.data.user?.token;
|
||||
let created_by = $page.data.user?.name ?? "User";
|
||||
|
||||
// Get the HTML from the tiptap editor
|
||||
if (editorRef) {
|
||||
@ -39,7 +39,8 @@
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="js">
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from "$app/stores"
|
||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||
import { isAuthenticated } from '$lib/stores/auth';
|
||||
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
try {
|
||||
// Make the GET request to your API
|
||||
const response = await fetch(PUBLIC_BASE_URL+`/document/${id}`);
|
||||
const response = await fetch(`${PUBLIC_BASE_URL}/api/document/${id}`);
|
||||
if (response.ok) {
|
||||
document = await response.json(); // Store document data
|
||||
} else {
|
||||
@ -36,7 +36,8 @@
|
||||
let agreeToDelete = false;
|
||||
// Function to delete a document by ID
|
||||
async function deleteDocument(id) {
|
||||
const url = PUBLIC_BASE_URL+`/documents/${id}`; // Update with your API base URL if different
|
||||
const url = `${PUBLIC_BASE_URL}/api/document/${id}`; // Update with your API base URL if different
|
||||
const token = $page.data.user?.token;
|
||||
|
||||
// Show the confirmation popup
|
||||
alertPopup = true;
|
||||
@ -60,6 +61,7 @@ async function deleteDocument(id) {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
@ -125,7 +127,7 @@ async function deleteDocument(id) {
|
||||
<div class="flex flex-col justify-between">
|
||||
<div class="w-full flex flex-row justify-between">
|
||||
<h1 class="text-2xl">{document.title}</h1>
|
||||
{#if $isAuthenticated}
|
||||
{#if $page.data.user.name}
|
||||
<div>
|
||||
<a href="/edit?id={id}"><button class="bg-yellow-500 hover:bg-blue-400 border-double border-blue-100 border-2 p-3 rounded-full px-4 text-white font-roboto"><i class="fa-solid fa-pencil fa-lg"></i></button></a>
|
||||
<button class="bg-red-500 hover:bg-blue-400 border-double border-blue-100 border-2 p-3 rounded-full px-4 text-white font-roboto" on:click={() => { deleteDocument(id)}}><i class="fa-solid fa-trash fa-lg"></i></button>
|
||||
@ -150,10 +152,10 @@ async function deleteDocument(id) {
|
||||
{/if}
|
||||
</div>
|
||||
<div class="">
|
||||
{#if document.created_at}
|
||||
<p class="font-semibold">Created: {new Date(document.created_at).toLocaleString()}</p>
|
||||
{#if document.updated_at}
|
||||
<p class="font-semibold">Updated: {new Date(document.updated_at).toLocaleString()}</p>
|
||||
{#if document.createdAt}
|
||||
<p class="font-semibold">Created: {new Date(document.createdAt).toLocaleString()}</p>
|
||||
{#if document.edited_by}
|
||||
<p class="font-semibold">Updated: {new Date(document.updatedAt).toLocaleString()}</p>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -14,14 +14,12 @@
|
||||
let title = "";
|
||||
|
||||
let document = null; // To store fetched document data
|
||||
let error = null; // To store any error that occurs during fetch
|
||||
let documentId;
|
||||
let tags = [];
|
||||
|
||||
// Function to fetch the document by ID
|
||||
async function fetchDocumentById(id) {
|
||||
try {
|
||||
const response = await fetch(PUBLIC_BASE_URL+`/document/${id}`);
|
||||
const response = await fetch(`${PUBLIC_BASE_URL}/api/document/${id}`);
|
||||
if (response.ok) {
|
||||
const fetchedDocument = await response.json();
|
||||
document = fetchedDocument; // Update document data
|
||||
@ -41,7 +39,6 @@
|
||||
onMount(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const id = urlParams.get('id'); // Get the `id` from the query string
|
||||
documentId = id;
|
||||
|
||||
if (!id) {
|
||||
error = 'ID is required';
|
||||
@ -57,11 +54,11 @@
|
||||
async function save() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const id = urlParams.get('id'); // Get the `id` from the query string
|
||||
const url = PUBLIC_BASE_URL+"/document/"+id;
|
||||
|
||||
const url = `${PUBLIC_BASE_URL}/api/document/`+id;
|
||||
console.log("Title value:", title);
|
||||
|
||||
let edited_by = $page.data.session.user?.name ?? "User";
|
||||
const edited_by = $page.data.user?.name;
|
||||
const token = $page.data.user?.token;
|
||||
|
||||
// Get the HTML from the tiptap editor
|
||||
if (editorRef) {
|
||||
@ -86,7 +83,8 @@
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
@ -2,7 +2,6 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from "$app/stores"
|
||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||
import { isAuthenticated } from '$lib/stores/auth';
|
||||
|
||||
let query = false;
|
||||
let searchQuery = "";
|
||||
@ -25,7 +24,7 @@
|
||||
// Function to simulate search API call
|
||||
async function searchDocuments(query) {
|
||||
try {
|
||||
const res = await fetch(PUBLIC_BASE_URL+`/search?query=${query}`);
|
||||
const res = await fetch(`${PUBLIC_BASE_URL}/api/document/search?query=${query}`);
|
||||
const data = await res.json();
|
||||
|
||||
if (data.length > 0) {
|
||||
@ -42,7 +41,7 @@
|
||||
// Function to fetch all documents from the API
|
||||
async function getAllDocuments() {
|
||||
try {
|
||||
const res = await fetch(PUBLIC_BASE_URL+`/document`);
|
||||
const res = await fetch(`${PUBLIC_BASE_URL}/api/document`);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch documents: ${res.statusText}`);
|
||||
@ -81,7 +80,7 @@
|
||||
</svelte:head>
|
||||
|
||||
<div class="flex items-center justify-center h-full flex-col">
|
||||
{#if $isAuthenticated}
|
||||
{#if $page.data.user.role == "admin"}
|
||||
<div class="flex flex-row justify-end w-full">
|
||||
<a href="/add" class="bg-green-500 hover:bg-blue-400 border-double border-blue-100 border-2 p-3 rounded-full px-4 mr-4 mt-3 text-white font-roboto">
|
||||
<i class="fa-solid fa-folder-plus fa-lg"></i>
|
||||
|
@ -1,6 +1,5 @@
|
||||
<script>
|
||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||
|
||||
async function handleSignIn(event) {
|
||||
event.preventDefault(); // Prevent the default form submission
|
||||
|
||||
@ -11,7 +10,7 @@
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
|
||||
try {
|
||||
const response = await fetch(`${PUBLIC_BASE_URL}/auth/login`, {
|
||||
const response = await fetch(`${PUBLIC_BASE_URL}/api/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
27
frontend/src/routes/logout/+page.server.ts
Normal file
27
frontend/src/routes/logout/+page.server.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
|
||||
export const load = async () => {
|
||||
// we only use this endpoint for the api
|
||||
// and don't need to see the page
|
||||
redirect(302, '/')
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
default({ cookies }) {
|
||||
cookies.set('token', '', {
|
||||
path: '/',
|
||||
expires: new Date(0),
|
||||
})
|
||||
cookies.set('name', '', {
|
||||
path: '/',
|
||||
expires: new Date(0),
|
||||
})
|
||||
cookies.set('email', '', {
|
||||
path: '/',
|
||||
expires: new Date(0),
|
||||
})
|
||||
|
||||
// redirect the user
|
||||
redirect(302, '/login')
|
||||
},
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
import { signIn } from "../../auth"
|
||||
import type { Actions } from "./$types"
|
||||
export const actions: Actions = { default: signIn }
|
@ -1,12 +0,0 @@
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const POST: RequestHandler = async ({ cookies }) => {
|
||||
try {
|
||||
// Clear the authToken cookie
|
||||
cookies.delete('authToken', { path: '/' });
|
||||
return new Response(JSON.stringify({ success: true }), { status: 200 });
|
||||
} catch (error) {
|
||||
console.error('Error during sign out:', error);
|
||||
return new Response(JSON.stringify({ success: false, message: 'Failed to sign out.' }), { status: 500 });
|
||||
}
|
||||
};
|
@ -1,6 +1,5 @@
|
||||
<script>
|
||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||
|
||||
async function handleSignup(event) {
|
||||
event.preventDefault(); // Prevent the default form submission
|
||||
|
||||
@ -11,7 +10,7 @@
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
|
||||
try {
|
||||
const response = await fetch(`${PUBLIC_BASE_URL}/user`, {
|
||||
const response = await fetch(`${PUBLIC_BASE_URL}/api/user`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
Loading…
Reference in New Issue
Block a user