Initial commit in this repository

This commit is contained in:
Rapturate
2026-04-27 22:16:17 -04:00
commit 68e7058ca4
64 changed files with 20817 additions and 0 deletions

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

7
src/config/db.js Normal file
View File

@@ -0,0 +1,7 @@
import { PrismaClient } from '../generated/prisma.js/client.js';
import { PrismaPg } from '@prisma/adapter-pg';
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
const prisma = new PrismaClient({ adapter });
export default prisma;

View File

@@ -0,0 +1,13 @@
import { signUp, logIn } from '../services/authService.js';
export async function signUpHandler(req, res) {
const { username, password } = req.body;
const newUser = await signUp(username, password);
res.status(201).json(newUser);
}
export async function logInHandler(req, res) {
const { username, password } = req.body;
const accessToken = await logIn(username, password);
res.status(200).json({ accessToken });
}

View File

@@ -0,0 +1,43 @@
import {
getAllIdeas,
createIdea,
updateIdea,
deleteIdea,
} from '../services/ideasService.js';
export async function getAllIdeasHandler(req, res) {
const {
search = '',
sortBy = 'date_created',
order = 'desc',
offset = '0',
limit = '10',
} = req.query;
const options = {
search,
sortBy,
order,
offset: parseInt(offset),
limit: parseInt(limit),
};
const ideas = await getAllIdeas(req.user.id, options);
res.status(200).json(ideas);
}
export async function createIdeaHandler(req, res) {
const { name, description } = req.body;
const newIdea = await createIdea(req.user.id, { name, description });
res.status(201).json(newIdea);
}
export async function updateIdeaHandler(req, res) {
const id = parseInt(req.params.id);
const { name, description } = req.body;
const updatedIdea = await updateIdea(id, req.user.id, { name, description });
res.status(200).json(updatedIdea);
}
export async function deleteIdeaHandler(req, res) {
const id = parseInt(req.params.id);
await deleteIdea(id, req.user.id);
res.status(204).send();
}

View File

@@ -0,0 +1,79 @@
import {
getAllMaterials,
getMaterialsByProject,
getMaterialById,
createMaterial,
deleteMaterial,
} from '../services/materialsService.js';
export async function getAllMaterialsHandler(req, res) {
const {
search = '',
sortBy = 'id',
order = 'desc',
offset = '0',
limit = '10',
} = req.query;
const options = {
search,
sortBy,
order,
offset: parseInt(offset),
limit: parseInt(limit),
};
const materials = await getAllMaterials(req.user.id, options);
res.status(200).json(materials);
}
export async function getMaterialsByProjectHandler(req, res) {
const projectId = parseInt(req.params.projectId);
const {
search = '',
sortBy = 'id',
order = 'desc',
offset = '0',
limit = '10',
} = req.query;
const options = {
search,
sortBy,
order,
offset: parseInt(offset),
limit: parseInt(limit),
};
const materials = await getMaterialsByProject(
projectId,
req.user.id,
options
);
res.status(200).json(materials);
}
export async function getMaterialByIdHandler(req, res) {
const id = parseInt(req.params.id);
const material = await getMaterialById(id, req.user.id);
res.status(200).json(material);
}
export async function createMaterialHandler(req, res) {
const { projectId, name, description, source, author, text } = req.body;
const newMaterial = await createMaterial(req.user.id, {
projectId,
name,
description,
source,
author,
text,
});
res.status(201).json(newMaterial);
}
export async function deleteMaterialHandler(req, res) {
const id = parseInt(req.params.id);
await deleteMaterial(id, req.user.id);
res.status(204).send();
}

View File

@@ -0,0 +1,159 @@
import {
getAllProjects,
getProjectById,
createProject,
updateProject,
deleteProject,
getProjectFilesById,
addProjectFile,
deleteProjectFile,
getProjectFilesForDownload,
} from '../services/projectsService.js';
import archiver from 'archiver';
import { Buffer } from 'node:buffer';
export async function getAllProjectsHandler(req, res) {
const {
search = '',
sortBy = 'date_created',
order = 'desc',
offset = '0',
limit = '10',
} = req.query;
const options = {
search,
sortBy,
order,
offset: parseInt(offset),
limit: parseInt(limit),
};
const projects = await getAllProjects(req.user.id, options);
res.status(200).json(projects);
}
export async function getProjectByIdHandler(req, res) {
const id = parseInt(req.params.id);
const project = await getProjectById(id, req.user.id);
res.status(200).json(project);
}
export async function createProjectHandler(req, res) {
const { name, description } = req.body;
const newProject = await createProject(req.user.id, { name, description });
res.status(201).json(newProject);
}
export async function updateProjectHandler(req, res) {
const id = parseInt(req.params.id);
const { name, description } = req.body;
const updatedProject = await updateProject(id, req.user.id, {
name,
description,
});
res.status(200).json(updatedProject);
}
export async function deleteProjectHandler(req, res) {
const id = parseInt(req.params.id);
await deleteProject(id, req.user.id);
res.status(204).send();
}
export async function getProjectFilesHandler(req, res) {
const id = parseInt(req.params.id);
const files = await getProjectFilesById(id, req.user.id);
res.status(200).json(files);
}
export async function addProjectFileHandler(req, res) {
let id;
let fileData;
try {
id = parseInt(req.params.id);
if (!req.file) {
return res.status(400).json({ error: 'No file provided' });
}
fileData = {
name: req.file.originalname,
file: req.file.buffer,
size: req.file.size,
mimeType: req.file.mimetype
};
} catch (error) {
return res.status(400).json({ error: 'Invalid file data' });
}
try {
const newFile = await addProjectFile(id, req.user.id, fileData);
if (!newFile) {
return res.status(404).json({ error: 'Project file error' });
}
const { file, ...metadata } = newFile;
res.status(201).json(metadata);
} catch (error) {
res.status(error.status || 500).json({ error: error.message });
}
}
export async function deleteProjectFileHandler(req, res) {
try {
const fileId = parseInt(req.params.id);
const deletedFile = await deleteProjectFile(fileId, req.user.id);
if (!deletedFile) {
return res.status(404).json({ error: 'File not found or unauthorized' });
}
res.status(200).json({ message: 'File deleted successfully' });
} catch (error) {
res.status(400).json({ error: 'Invalid file ID' });
}
}
export async function downloadProjectFilesHandler(req, res) {
try {
const fileId = parseInt(req.params.id);
const userId = req.user.id;
const files = await getProjectFilesForDownload(fileId, userId);
if (!files || files.length === 0) {
return res.status(404).json({ error: 'No files found for this project' });
}
res.set({
'Content-Type': 'application/zip',
'Content-Disposition': `attachment; filename="project_${fileId}_files.zip"`
});
const archive = archiver('zip', { zlib: { level: 9 } });
archive.pipe(res);
files.forEach(f => {
const binaryData = f.file;
if (binaryData) {
const bufferData = Buffer.from(binaryData);
archive.append(bufferData, { name: f.name });
}
else {
console.error(`ERROR: No binary data found for ${f.name}. Object was:`, f);
}
});
await archive.finalize();
} catch (error) {
if (!res.headersSent) {
res.status(error.status || 500).json({ error: error.message });
}
}
}

1
src/generated/prisma.js/client.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export * from "./index"

View File

@@ -0,0 +1,5 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
module.exports = { ...require('.') }

1
src/generated/prisma.js/default.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export * from "./index"

View File

@@ -0,0 +1,5 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
module.exports = { ...require('#main-entry-point') }

1
src/generated/prisma.js/edge.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export * from "./default"

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,212 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
Object.defineProperty(exports, "__esModule", { value: true });
const {
Decimal,
DbNull,
JsonNull,
AnyNull,
NullTypes,
makeStrictEnum,
Public,
getRuntime,
skip
} = require('./runtime/index-browser.js')
const Prisma = {}
exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 7.8.0
* Query Engine version: 3c6e192761c0362d496ed980de936e2f3cebcd3a
*/
Prisma.prismaVersion = {
client: "7.8.0",
engine: "3c6e192761c0362d496ed980de936e2f3cebcd3a"
}
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 = DbNull
Prisma.JsonNull = JsonNull
Prisma.AnyNull = AnyNull
Prisma.NullTypes = NullTypes
/**
* Enums
*/
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable'
});
exports.Prisma.UserScalarFieldEnum = {
id: 'id',
username: 'username',
password: 'password'
};
exports.Prisma.IdeaScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
date_created: 'date_created',
userId: 'userId'
};
exports.Prisma.ProjectScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
date_created: 'date_created',
userId: 'userId'
};
exports.Prisma.MaterialScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
source: 'source',
author: 'author',
text: 'text',
projectId: 'projectId'
};
exports.Prisma.FileScalarFieldEnum = {
id: 'id',
name: 'name',
file: 'file',
size: 'size',
mimeType: 'mimeType',
projectId: 'projectId'
};
exports.Prisma.SortOrder = {
asc: 'asc',
desc: 'desc'
};
exports.Prisma.QueryMode = {
default: 'default',
insensitive: 'insensitive'
};
exports.Prisma.ModelName = {
User: 'User',
Idea: 'Idea',
Project: 'Project',
Material: 'Material',
File: 'File'
};
/**
* 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)

8890
src/generated/prisma.js/index.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,144 @@
{
"name": "prisma-client-8a7d0bd0282d327d88c7d480c03ad5f66befdf72c92e9b6d7469f459b777fd67",
"main": "index.js",
"types": "index.d.ts",
"browser": "default.js",
"exports": {
"./client": {
"require": {
"node": "./index.js",
"edge-light": "./edge.js",
"workerd": "./edge.js",
"worker": "./edge.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./edge.js",
"workerd": "./edge.js",
"worker": "./edge.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
},
"./package.json": "./package.json",
".": {
"require": {
"node": "./index.js",
"edge-light": "./edge.js",
"workerd": "./edge.js",
"worker": "./edge.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./edge.js",
"workerd": "./edge.js",
"worker": "./edge.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.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"
},
"./edge": {
"types": "./edge.d.ts",
"require": "./edge.js",
"import": "./edge.js",
"default": "./edge.js"
},
"./runtime/client": {
"types": "./runtime/client.d.ts",
"node": {
"require": "./runtime/client.js",
"default": "./runtime/client.js"
},
"require": "./runtime/client.js",
"import": "./runtime/client.mjs",
"default": "./runtime/client.mjs"
},
"./runtime/wasm-compiler-edge": {
"types": "./runtime/wasm-compiler-edge.d.ts",
"require": "./runtime/wasm-compiler-edge.js",
"import": "./runtime/wasm-compiler-edge.mjs",
"default": "./runtime/wasm-compiler-edge.mjs"
},
"./runtime/index-browser": {
"types": "./runtime/index-browser.d.ts",
"require": "./runtime/index-browser.js",
"import": "./runtime/index-browser.mjs",
"default": "./runtime/index-browser.mjs"
},
"./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": "7.8.0",
"sideEffects": false,
"dependencies": {
"@prisma/client-runtime-utils": "7.8.0"
},
"imports": {
"#wasm-compiler-loader": {
"edge-light": "./wasm-edge-light-loader.mjs",
"workerd": "./wasm-worker-loader.mjs",
"worker": "./wasm-worker-loader.mjs",
"default": "./wasm-worker-loader.mjs"
},
"#main-entry-point": {
"require": {
"node": "./index.js",
"edge-light": "./edge.js",
"workerd": "./edge.js",
"worker": "./edge.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./edge.js",
"workerd": "./edge.js",
"worker": "./edge.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
}
}
}

View File

@@ -0,0 +1,2 @@
"use strict";var h=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var D=(e,t)=>{for(var n in t)h(e,n,{get:t[n],enumerable:!0})},O=(e,t,n,_)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of M(t))!j.call(e,r)&&r!==n&&h(e,r,{get:()=>t[r],enumerable:!(_=T(t,r))||_.enumerable});return e};var B=e=>O(h({},"__esModule",{value:!0}),e);var xe={};D(xe,{QueryCompiler:()=>F,__wbg_Error_e83987f665cf5504:()=>q,__wbg_Number_bb48ca12f395cd08:()=>C,__wbg_String_8f0eb39a4a4c2f66:()=>k,__wbg___wbindgen_boolean_get_6d5a1ee65bab5f68:()=>W,__wbg___wbindgen_debug_string_df47ffb5e35e6763:()=>V,__wbg___wbindgen_in_bb933bd9e1b3bc0f:()=>z,__wbg___wbindgen_is_object_c818261d21f283a4:()=>L,__wbg___wbindgen_is_string_fbb76cb2940daafd:()=>P,__wbg___wbindgen_is_undefined_2d472862bd29a478:()=>Q,__wbg___wbindgen_jsval_loose_eq_b664b38a2f582147:()=>Y,__wbg___wbindgen_number_get_a20bf9b85341449d:()=>G,__wbg___wbindgen_string_get_e4f06c90489ad01b:()=>J,__wbg___wbindgen_throw_b855445ff6a94295:()=>X,__wbg_entries_e171b586f8f6bdbf:()=>H,__wbg_getTime_14776bfb48a1bff9:()=>K,__wbg_get_7bed016f185add81:()=>Z,__wbg_get_with_ref_key_1dc361bd10053bfe:()=>v,__wbg_instanceof_ArrayBuffer_70beb1189ca63b38:()=>ee,__wbg_instanceof_Uint8Array_20c8e73002f7af98:()=>te,__wbg_isSafeInteger_d216eda7911dde36:()=>ne,__wbg_length_69bca3cb64fc8748:()=>re,__wbg_length_cdd215e10d9dd507:()=>_e,__wbg_new_0_f9740686d739025c:()=>oe,__wbg_new_1acc0b6eea89d040:()=>ce,__wbg_new_5a79be3ab53b8aa5:()=>ie,__wbg_new_68651c719dcda04e:()=>se,__wbg_new_e17d9f43105b08be:()=>ue,__wbg_prototypesetcall_2a6620b6922694b2:()=>fe,__wbg_set_3f1d0b984ed272ed:()=>be,__wbg_set_907fb406c34a251d:()=>de,__wbg_set_c213c871859d6500:()=>ae,__wbg_set_message_82ae475bb413aa5c:()=>ge,__wbg_set_wasm:()=>N,__wbindgen_cast_2241b6af4c4b2941:()=>le,__wbindgen_cast_4625c577ab2ec9ee:()=>we,__wbindgen_cast_9ae0607507abb057:()=>pe,__wbindgen_cast_d6cd19b81560fd6e:()=>ye,__wbindgen_init_externref_table:()=>me});module.exports=B(xe);var A=()=>{};A.prototype=A;let o;function N(e){o=e}let p=null;function a(){return(p===null||p.byteLength===0)&&(p=new Uint8Array(o.memory.buffer)),p}let y=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0});y.decode();const U=2146435072;let S=0;function R(e,t){return S+=t,S>=U&&(y=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0}),y.decode(),S=t),y.decode(a().subarray(e,e+t))}function m(e,t){return e=e>>>0,R(e,t)}let f=0;const g=new TextEncoder;"encodeInto"in g||(g.encodeInto=function(e,t){const n=g.encode(e);return t.set(n),{read:e.length,written:n.length}});function l(e,t,n){if(n===void 0){const i=g.encode(e),d=t(i.length,1)>>>0;return a().subarray(d,d+i.length).set(i),f=i.length,d}let _=e.length,r=t(_,1)>>>0;const s=a();let c=0;for(;c<_;c++){const i=e.charCodeAt(c);if(i>127)break;s[r+c]=i}if(c!==_){c!==0&&(e=e.slice(c)),r=n(r,_,_=c+e.length*3,1)>>>0;const i=a().subarray(r+c,r+_),d=g.encodeInto(e,i);c+=d.written,r=n(r,_,c,1)>>>0}return f=c,r}let b=null;function u(){return(b===null||b.buffer.detached===!0||b.buffer.detached===void 0&&b.buffer!==o.memory.buffer)&&(b=new DataView(o.memory.buffer)),b}function x(e){return e==null}function I(e){const t=typeof e;if(t=="number"||t=="boolean"||e==null)return`${e}`;if(t=="string")return`"${e}"`;if(t=="symbol"){const r=e.description;return r==null?"Symbol":`Symbol(${r})`}if(t=="function"){const r=e.name;return typeof r=="string"&&r.length>0?`Function(${r})`:"Function"}if(Array.isArray(e)){const r=e.length;let s="[";r>0&&(s+=I(e[0]));for(let c=1;c<r;c++)s+=", "+I(e[c]);return s+="]",s}const n=/\[object ([^\]]+)\]/.exec(toString.call(e));let _;if(n&&n.length>1)_=n[1];else return toString.call(e);if(_=="Object")try{return"Object("+JSON.stringify(e)+")"}catch{return"Object"}return e instanceof Error?`${e.name}: ${e.message}
${e.stack}`:_}function $(e,t){return e=e>>>0,a().subarray(e/1,e/1+t)}function w(e){const t=o.__wbindgen_externrefs.get(e);return o.__externref_table_dealloc(e),t}const E=typeof FinalizationRegistry>"u"?{register:()=>{},unregister:()=>{}}:new FinalizationRegistry(e=>o.__wbg_querycompiler_free(e>>>0,1));class F{__destroy_into_raw(){const t=this.__wbg_ptr;return this.__wbg_ptr=0,E.unregister(this),t}free(){const t=this.__destroy_into_raw();o.__wbg_querycompiler_free(t,0)}compileBatch(t){const n=l(t,o.__wbindgen_malloc,o.__wbindgen_realloc),_=f,r=o.querycompiler_compileBatch(this.__wbg_ptr,n,_);if(r[2])throw w(r[1]);return w(r[0])}constructor(t){const n=o.querycompiler_new(t);if(n[2])throw w(n[1]);return this.__wbg_ptr=n[0]>>>0,E.register(this,this.__wbg_ptr,this),this}compile(t){const n=l(t,o.__wbindgen_malloc,o.__wbindgen_realloc),_=f,r=o.querycompiler_compile(this.__wbg_ptr,n,_);if(r[2])throw w(r[1]);return w(r[0])}}Symbol.dispose&&(F.prototype[Symbol.dispose]=F.prototype.free);function q(e,t){return Error(m(e,t))}function C(e){return Number(e)}function k(e,t){const n=String(t),_=l(n,o.__wbindgen_malloc,o.__wbindgen_realloc),r=f;u().setInt32(e+4*1,r,!0),u().setInt32(e+4*0,_,!0)}function W(e){const t=e,n=typeof t=="boolean"?t:void 0;return x(n)?16777215:n?1:0}function V(e,t){const n=I(t),_=l(n,o.__wbindgen_malloc,o.__wbindgen_realloc),r=f;u().setInt32(e+4*1,r,!0),u().setInt32(e+4*0,_,!0)}function z(e,t){return e in t}function L(e){const t=e;return typeof t=="object"&&t!==null}function P(e){return typeof e=="string"}function Q(e){return e===void 0}function Y(e,t){return e==t}function G(e,t){const n=t,_=typeof n=="number"?n:void 0;u().setFloat64(e+8*1,x(_)?0:_,!0),u().setInt32(e+4*0,!x(_),!0)}function J(e,t){const n=t,_=typeof n=="string"?n:void 0;var r=x(_)?0:l(_,o.__wbindgen_malloc,o.__wbindgen_realloc),s=f;u().setInt32(e+4*1,s,!0),u().setInt32(e+4*0,r,!0)}function X(e,t){throw new Error(m(e,t))}function H(e){return Object.entries(e)}function K(e){return e.getTime()}function Z(e,t){return e[t>>>0]}function v(e,t){return e[t]}function ee(e){let t;try{t=e instanceof ArrayBuffer}catch{t=!1}return t}function te(e){let t;try{t=e instanceof Uint8Array}catch{t=!1}return t}function ne(e){return Number.isSafeInteger(e)}function re(e){return e.length}function _e(e){return e.length}function oe(){return new Date}function ce(){return new Object}function ie(e){return new Uint8Array(e)}function se(){return new Map}function ue(){return new Array}function fe(e,t,n){Uint8Array.prototype.set.call($(e,t),n)}function be(e,t,n){e[t]=n}function de(e,t,n){return e.set(t,n)}function ae(e,t,n){e[t>>>0]=n}function ge(e,t){global.PRISMA_WASM_PANIC_REGISTRY.set_message(m(e,t))}function le(e,t){return m(e,t)}function we(e){return BigInt.asUintN(64,e)}function pe(e){return e}function ye(e){return e}function me(){const e=o.__wbindgen_externrefs,t=e.grow(4);e.set(0,void 0),e.set(t+0,void 0),e.set(t+1,null),e.set(t+2,!0),e.set(t+3,!1)}0&&(module.exports={QueryCompiler,__wbg_Error_e83987f665cf5504,__wbg_Number_bb48ca12f395cd08,__wbg_String_8f0eb39a4a4c2f66,__wbg___wbindgen_boolean_get_6d5a1ee65bab5f68,__wbg___wbindgen_debug_string_df47ffb5e35e6763,__wbg___wbindgen_in_bb933bd9e1b3bc0f,__wbg___wbindgen_is_object_c818261d21f283a4,__wbg___wbindgen_is_string_fbb76cb2940daafd,__wbg___wbindgen_is_undefined_2d472862bd29a478,__wbg___wbindgen_jsval_loose_eq_b664b38a2f582147,__wbg___wbindgen_number_get_a20bf9b85341449d,__wbg___wbindgen_string_get_e4f06c90489ad01b,__wbg___wbindgen_throw_b855445ff6a94295,__wbg_entries_e171b586f8f6bdbf,__wbg_getTime_14776bfb48a1bff9,__wbg_get_7bed016f185add81,__wbg_get_with_ref_key_1dc361bd10053bfe,__wbg_instanceof_ArrayBuffer_70beb1189ca63b38,__wbg_instanceof_Uint8Array_20c8e73002f7af98,__wbg_isSafeInteger_d216eda7911dde36,__wbg_length_69bca3cb64fc8748,__wbg_length_cdd215e10d9dd507,__wbg_new_0_f9740686d739025c,__wbg_new_1acc0b6eea89d040,__wbg_new_5a79be3ab53b8aa5,__wbg_new_68651c719dcda04e,__wbg_new_e17d9f43105b08be,__wbg_prototypesetcall_2a6620b6922694b2,__wbg_set_3f1d0b984ed272ed,__wbg_set_907fb406c34a251d,__wbg_set_c213c871859d6500,__wbg_set_message_82ae475bb413aa5c,__wbg_set_wasm,__wbindgen_cast_2241b6af4c4b2941,__wbindgen_cast_4625c577ab2ec9ee,__wbindgen_cast_9ae0607507abb057,__wbindgen_cast_d6cd19b81560fd6e,__wbindgen_init_externref_table});

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,90 @@
import { AnyNull } from '@prisma/client-runtime-utils';
import { DbNull } from '@prisma/client-runtime-utils';
import { Decimal } from '@prisma/client-runtime-utils';
import { isAnyNull } from '@prisma/client-runtime-utils';
import { isDbNull } from '@prisma/client-runtime-utils';
import { isJsonNull } from '@prisma/client-runtime-utils';
import { isObjectEnumValue } from '@prisma/client-runtime-utils';
import { JsonNull } from '@prisma/client-runtime-utils';
import { NullTypes } from '@prisma/client-runtime-utils';
export { 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;
export { DbNull }
export { Decimal }
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;
};
export { isAnyNull }
export { isDbNull }
export { isJsonNull }
export { isObjectEnumValue }
export { 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 | [];
export { NullTypes }
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 { }

View File

@@ -0,0 +1,6 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
"use strict";var s=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var f=Object.prototype.hasOwnProperty;var a=(e,t)=>{for(var n in t)s(e,n,{get:t[n],enumerable:!0})},y=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of p(t))!f.call(e,i)&&i!==n&&s(e,i,{get:()=>t[i],enumerable:!(r=g(t,i))||r.enumerable});return e};var x=e=>y(s({},"__esModule",{value:!0}),e);var M={};a(M,{AnyNull:()=>o.AnyNull,DbNull:()=>o.DbNull,Decimal:()=>m.Decimal,JsonNull:()=>o.JsonNull,NullTypes:()=>o.NullTypes,Public:()=>l,getRuntime:()=>c,isAnyNull:()=>o.isAnyNull,isDbNull:()=>o.isDbNull,isJsonNull:()=>o.isJsonNull,isObjectEnumValue:()=>o.isObjectEnumValue,makeStrictEnum:()=>u});module.exports=x(M);var l={};a(l,{validator:()=>d});function d(...e){return t=>t}var b=new Set(["toJSON","$$typeof","asymmetricMatch",Symbol.iterator,Symbol.toStringTag,Symbol.isConcatSpreadable,Symbol.toPrimitive]);function u(e){return new Proxy(e,{get(t,n){if(n in t)return t[n];if(!b.has(n))throw new TypeError("Invalid enum value: ".concat(String(n)))}})}var N=()=>{var e,t;return((t=(e=globalThis.process)==null?void 0:e.release)==null?void 0:t.name)==="node"},E=()=>{var e,t;return!!globalThis.Bun||!!((t=(e=globalThis.process)==null?void 0:e.versions)!=null&&t.bun)},S=()=>!!globalThis.Deno,R=()=>typeof globalThis.Netlify=="object",h=()=>typeof globalThis.EdgeRuntime=="object",C=()=>{var e;return((e=globalThis.navigator)==null?void 0:e.userAgent)==="Cloudflare-Workers"};function k(){var n;return(n=[[R,"netlify"],[h,"edge-light"],[C,"workerd"],[S,"deno"],[E,"bun"],[N,"node"]].flatMap(r=>r[0]()?[r[1]]:[]).at(0))!=null?n:""}var O={node:"Node.js",workerd:"Cloudflare Workers",deno:"Deno and Deno Deploy",netlify:"Netlify Edge Functions","edge-light":"Edge Runtime (Vercel Edge Functions, Vercel Edge Middleware, Next.js (Pages Router) Edge API Routes, Next.js (App Router) Edge Route Handlers or Next.js Middleware)"};function c(){let e=k();return{id:e,prettyName:O[e]||e,isEdge:["workerd","deno","netlify","edge-light"].includes(e)}}var o=require("@prisma/client-runtime-utils"),m=require("@prisma/client-runtime-utils");0&&(module.exports={AnyNull,DbNull,Decimal,JsonNull,NullTypes,Public,getRuntime,isAnyNull,isDbNull,isJsonNull,isObjectEnumValue,makeStrictEnum});
//# sourceMappingURL=index-browser.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,71 @@
generator client {
provider = "prisma-client-js"
output = "../src/generated/prisma.js"
}
datasource db {
provider = "postgresql"
}
model User {
id Int @id @default(autoincrement())
username String @unique
password String
ideas Idea[]
projects Project[]
@@map("users")
}
model Idea {
id Int @id @default(autoincrement())
name String
description String
date_created DateTime @default(now()) @map("date_created")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int @map("user_id")
@@index([userId])
@@map("ideas")
}
model Project {
id Int @id @default(autoincrement())
name String
description String
date_created DateTime @default(now()) @map("date_created")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int @map("user_id")
materials Material[]
files File[]
@@index([userId])
@@map("projects")
}
model Material {
id Int @id @default(autoincrement())
name String
description String
source String
author String
text String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
projectId Int @map("project_id")
@@index([projectId])
@@map("materials")
}
model File {
id Int @id @default(autoincrement())
name String
file Bytes
size Int
mimeType String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
projectId Int @map("project_id")
@@index([projectId])
@@map("files")
}

View File

@@ -0,0 +1,5 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
export default import('./query_compiler_fast_bg.wasm?module')

View File

@@ -0,0 +1,5 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
export default import('./query_compiler_fast_bg.wasm')

View File

@@ -0,0 +1,38 @@
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET) throw new Error('JWT_SECRET environment variable is not set');
export function authenticate(req, res, next) {
if (process.env.NODE_ENV === 'development') {
console.log('Auth middleware reached!');
}
const authHeader = req.headers.authorization;
if (process.env.NODE_ENV === 'development') {
console.log('Header found:', authHeader);
}
const err = new Error('Not authenticated. Please provide a valid token.');
err.status = 401;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return next(err);
}
const token = authHeader.split(' ')[1];
try {
const payload = jwt.verify(token, JWT_SECRET);
if (process.env.NODE_ENV === 'development') {
console.log('Decoded JWT Payload:', payload);
}
req.user = { id: payload.id, role: payload.role };
next();
} catch (error) {
return next(err);
}
}

View File

@@ -0,0 +1,17 @@
import { validationResult } from 'express-validator';
export function handleValidationErrors(req, res, next) {
const errors = validationResult(req);
if (!errors.isEmpty()) {
if (process.env.NODE_ENV === 'development') {
console.log('Validation errors:', errors.array());
}
return res.status(400).json({
errors: errors.array().map((err) => ({
field: err.path,
message: err.msg,
})),
});
}
next();
}

View File

@@ -0,0 +1,34 @@
import { body } from 'express-validator';
import { handleValidationErrors } from './handleValidationErrors.js';
export const validateSignUp = [
body('username')
.exists({ checkFalsy: true })
.withMessage('Username is required')
.bail()
.trim()
.escape()
.isLength({ min: 3 })
.withMessage('Username must be at least 3 characters'),
body('password')
.exists({ checkFalsy: true })
.withMessage('Password is required')
.bail()
.isLength({ min: 8 })
.withMessage('Password must be at least 8 characters'),
handleValidationErrors,
];
export const validateLogIn = [
body('username')
.exists({ checkFalsy: true })
.withMessage('Username is required'),
body('password')
.exists({ checkFalsy: true })
.withMessage('Password is required'),
handleValidationErrors,
];

View File

@@ -0,0 +1,93 @@
import { param, query, body, oneOf } from 'express-validator';
import { handleValidationErrors } from './handleValidationErrors.js';
export const validateGetAllQuery = [
query('search')
.optional()
.trim()
.isString()
.withMessage('Search must be a string'),
query('sortBy')
.optional()
.isIn(['name', 'date_created'])
.withMessage('sortBy must be one of: name, date_created'),
query('order')
.optional()
.isIn(['asc', 'desc'])
.withMessage('order must be asc or desc'),
query('offset')
.optional()
.isInt({ min: 0 })
.withMessage('offset must be a non-negative integer'),
query('limit')
.optional()
.isInt({ min: 1, max: 100 })
.withMessage('limit must be an integer between 1 and 100'),
handleValidationErrors,
];
export const validateId = [
param('id')
.trim()
.escape()
.isInt({ min: 1 })
.withMessage('Id must be a positive integer'),
handleValidationErrors,
];
export const validateCreateIdea = [
body('name')
.exists({ checkFalsy: true })
.withMessage('Name is required')
.bail()
.trim()
.escape()
.isLength({ min: 3 })
.withMessage('Name must be at least 3 characters'),
body('description')
.exists({ checkFalsy: true })
.withMessage('Description is required')
.bail()
.trim()
.escape()
.isString()
.withMessage('Description must be a string'),
handleValidationErrors,
];
export const validateUpdateIdea = [
oneOf(
[
body('name').exists({ checkFalsy: true }),
body('description').exists({ checkFalsy: true }),
],
{ message: 'At least one field (name, description) must be provided' }
),
body('name')
.optional()
.trim()
.escape()
.isString()
.withMessage('Name must be a string')
.bail()
.isLength({ min: 3 })
.withMessage('Name must be at least 3 characters'),
body('description')
.optional()
.trim()
.escape()
.isString()
.withMessage('Description must be a string'),
handleValidationErrors,
];

View File

@@ -0,0 +1,107 @@
import { param, query, body } from 'express-validator';
import { handleValidationErrors } from './handleValidationErrors.js';
export const validateGetAllQuery = [
query('search')
.optional()
.trim()
.isString()
.withMessage('Search must be a string'),
query('sortBy')
.optional()
.isIn(['name', 'id'])
.withMessage('sortBy must be one of: name, id'),
query('order')
.optional()
.isIn(['asc', 'desc'])
.withMessage('order must be asc or desc'),
query('offset')
.optional()
.isInt({ min: 0 })
.withMessage('offset must be a non-negative integer'),
query('limit')
.optional()
.isInt({ min: 1, max: 100 })
.withMessage('limit must be an integer between 1 and 100'),
handleValidationErrors,
];
export const validateId = [
param('id')
.trim()
.escape()
.isInt({ min: 1 })
.withMessage('Id must be a positive integer'),
handleValidationErrors,
];
export const validateProjectId = [
param('projectId')
.trim()
.escape()
.isInt({ min: 1 })
.withMessage('ProjectId must be a positive integer'),
handleValidationErrors,
];
export const validateCreateMaterial = [
body('projectId')
.exists({ checkFalsy: true })
.withMessage('ProjectId is required')
.bail()
.isInt({ min: 1 })
.withMessage('ProjectId must be a positive integer'),
body('name')
.exists({ checkFalsy: true })
.withMessage('Name is required')
.bail()
.trim()
.escape()
.isLength({ min: 3 })
.withMessage('Name must be at least 3 characters'),
body('description')
.exists({ checkFalsy: true })
.withMessage('Description is required')
.bail()
.trim()
.escape()
.isString()
.withMessage('Description must be a string'),
body('source')
.exists({ checkFalsy: true })
.withMessage('Source is required')
.bail()
.trim()
.escape()
.isString()
.withMessage('Source must be a string'),
body('author')
.exists({ checkFalsy: true })
.withMessage('Author is required')
.bail()
.trim()
.escape()
.isString()
.withMessage('Author must be a string'),
body('text')
.exists({ checkFalsy: true })
.withMessage('Text is required')
.bail()
.trim()
.escape()
.isString()
.withMessage('Text must be a string'),
handleValidationErrors,
];

View File

@@ -0,0 +1,93 @@
import { param, query, body, oneOf } from 'express-validator';
import { handleValidationErrors } from './handleValidationErrors.js';
export const validateGetAllQuery = [
query('search')
.optional()
.trim()
.isString()
.withMessage('Search must be a string'),
query('sortBy')
.optional()
.isIn(['name', 'date_created'])
.withMessage('sortBy must be one of: name, date_created'),
query('order')
.optional()
.isIn(['asc', 'desc'])
.withMessage('order must be asc or desc'),
query('offset')
.optional()
.isInt({ min: 0 })
.withMessage('offset must be a non-negative integer'),
query('limit')
.optional()
.isInt({ min: 1, max: 100 })
.withMessage('limit must be an integer between 1 and 100'),
handleValidationErrors,
];
export const validateId = [
param('id')
.trim()
.escape()
.isInt({ min: 1 })
.withMessage('Id must be a positive integer'),
handleValidationErrors,
];
export const validateCreateProject = [
body('name')
.exists({ checkFalsy: true })
.withMessage('Name is required')
.bail()
.trim()
.escape()
.isLength({ min: 3 })
.withMessage('Name must be at least 3 characters'),
body('description')
.exists({ checkFalsy: true })
.withMessage('Description is required')
.bail()
.trim()
.escape()
.isString()
.withMessage('Description must be a string'),
handleValidationErrors,
];
export const validateUpdateProject = [
oneOf(
[
body('name').exists({ checkFalsy: true }),
body('description').exists({ checkFalsy: true }),
],
{ message: 'At least one field (name, description) must be provided' }
),
body('name')
.optional()
.trim()
.escape()
.isString()
.withMessage('Name must be a string')
.bail()
.isLength({ min: 3 })
.withMessage('Name must be at least 3 characters'),
body('description')
.optional()
.trim()
.escape()
.isString()
.withMessage('Description must be a string'),
handleValidationErrors,
];

View File

@@ -0,0 +1,139 @@
import prisma from '../config/db.js';
const ALLOWED_SORT_FIELDS = ['name', 'date_created'];
export async function getAll(
userId,
{
search = '',
sortBy = 'date_created',
order = 'desc',
offset = 0,
limit = 10,
} = {}
) {
if (process.env.NODE_ENV === 'development') {
console.log('ideasRepo.getAll() called for userId:', userId);
}
const safeSortBy = ALLOWED_SORT_FIELDS.includes(sortBy)
? sortBy
: 'date_created';
const safeOrder = order === 'asc' ? 'asc' : 'desc';
const ideas = await prisma.idea.findMany({
where: {
userId,
...(search ? { name: { contains: search, mode: 'insensitive' } } : {}),
},
orderBy: { [safeSortBy]: safeOrder },
skip: offset,
take: limit,
});
return ideas;
}
export async function getById(ideaId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log(
'ideasRepo.getById() called for ideaId:',
ideaId,
'userId:',
userId
);
}
const idea = await prisma.idea.findUnique({
where: { id: ideaId },
});
if (idea && idea.userId !== userId) {
return null;
}
return idea;
}
export async function create(userId, ideaData) {
if (process.env.NODE_ENV === 'development') {
console.log('ideasRepo.create() called with data:', ideaData);
}
const newIdea = await prisma.idea.create({
data: {
...ideaData,
userId,
},
});
return newIdea;
}
export async function update(ideaId, userId, updatedData) {
if (process.env.NODE_ENV === 'development') {
console.log(
'ideasRepo.update() called for ideaId:',
ideaId,
'with data:',
updatedData
);
}
try {
const idea = await prisma.idea.findUnique({
where: { id: ideaId },
});
if (!idea || idea.userId !== userId) {
return null;
}
const updatedIdea = await prisma.idea.update({
where: { id: ideaId },
data: updatedData,
});
return updatedIdea;
} catch (error) {
if (error.code === 'P2025') return null;
throw error;
}
}
export async function remove(ideaId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log('ideasRepo.remove() called for ideaId:', ideaId);
}
try {
const idea = await prisma.idea.findUnique({
where: { id: ideaId },
});
if (!idea || idea.userId !== userId) {
return null;
}
const deletedIdea = await prisma.idea.delete({
where: { id: ideaId },
});
return deletedIdea;
} catch (error) {
if (error.code === 'P2025') return null;
throw error;
}
}
export async function existsByName(userId, name) {
if (process.env.NODE_ENV === 'development') {
console.log('ideasRepo.existsByName() called for name:', name);
}
const idea = await prisma.idea.findFirst({
where: { userId, name },
});
return idea !== null;
}

View File

@@ -0,0 +1,142 @@
import prisma from '../config/db.js';
const ALLOWED_SORT_FIELDS = ['name', 'id'];
export async function getAll(
userId,
{ search = '', sortBy = 'id', order = 'desc', offset = 0, limit = 10 } = {}
) {
if (process.env.NODE_ENV === 'development') {
console.log('materialsRepo.getAll() called for userId:', userId);
}
const safeSortBy = ALLOWED_SORT_FIELDS.includes(sortBy) ? sortBy : 'id';
const safeOrder = order === 'asc' ? 'asc' : 'desc';
const materials = await prisma.material.findMany({
where: {
project: { userId },
...(search ? { name: { contains: search, mode: 'insensitive' } } : {}),
},
orderBy: { [safeSortBy]: safeOrder },
skip: offset,
take: limit,
});
return materials;
}
export async function getByProjectId(
projectId,
userId,
{ search = '', sortBy = 'id', order = 'desc', offset = 0, limit = 10 } = {}
) {
if (process.env.NODE_ENV === 'development') {
console.log(
'materialsRepo.getByProjectId() called for projectId:',
projectId,
'userId:',
userId
);
}
const safeSortBy = ALLOWED_SORT_FIELDS.includes(sortBy) ? sortBy : 'id';
const safeOrder = order === 'asc' ? 'asc' : 'desc';
const materials = await prisma.material.findMany({
where: {
projectId,
project: { userId },
...(search ? { name: { contains: search, mode: 'insensitive' } } : {}),
},
orderBy: { [safeSortBy]: safeOrder },
skip: offset,
take: limit,
});
return materials;
}
export async function getById(materialId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log(
'materialsRepo.getById() called for materialId:',
materialId,
'userId:',
userId
);
}
const material = await prisma.material.findUnique({
where: { id: materialId },
include: { project: true },
});
if (material && material.project.userId !== userId) {
return null;
}
return material;
}
export async function create(userId, materialData) {
if (process.env.NODE_ENV === 'development') {
console.log('materialsRepo.create() called with data:', materialData);
}
try {
const project = await prisma.project.findUnique({
where: { id: materialData.projectId },
});
if (!project || project.userId !== userId) {
return null;
}
const newMaterial = await prisma.material.create({
data: materialData,
});
return newMaterial;
} catch (error) {
throw error;
}
}
export async function remove(materialId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log('materialsRepo.remove() called for materialId:', materialId);
}
try {
const material = await prisma.material.findUnique({
where: { id: materialId },
include: { project: true },
});
if (!material || material.project.userId !== userId) {
return null;
}
const deletedMaterial = await prisma.material.delete({
where: { id: materialId },
});
return deletedMaterial;
} catch (error) {
if (error.code === 'P2025') return null;
throw error;
}
}
export async function existsByName(userId, name) {
if (process.env.NODE_ENV === 'development') {
console.log('materialsRepo.existsByName() called for name:', name);
}
const material = await prisma.material.findFirst({
where: { name, project: { userId } },
});
return material !== null;
}

View File

@@ -0,0 +1,242 @@
import prisma from '../config/db.js';
const ALLOWED_SORT_FIELDS = ['name', 'date_created'];
export async function getAll(
userId,
{
search = '',
sortBy = 'date_created',
order = 'desc',
offset = 0,
limit = 10,
} = {}
) {
if (process.env.NODE_ENV === 'development') {
console.log('projectsRepo.getAll() called for userId:', userId);
}
const safeSortBy = ALLOWED_SORT_FIELDS.includes(sortBy)
? sortBy
: 'date_created';
const safeOrder = order === 'asc' ? 'asc' : 'desc';
const projects = await prisma.project.findMany({
where: {
userId,
...(search ? { name: { contains: search, mode: 'insensitive' } } : {}),
},
orderBy: { [safeSortBy]: safeOrder },
skip: offset,
take: limit,
include: { files: true },
});
return projects;
}
export async function getById(projectId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log(
'projectsRepo.getById() called for projectId:',
projectId,
'userId:',
userId
);
}
const project = await prisma.project.findUnique({
where: { id: projectId },
include: { files: true },
});
if (project && project.userId !== userId) {
return null;
}
return project;
}
export async function create(userId, projectData) {
if (process.env.NODE_ENV === 'development') {
console.log('projectsRepo.create() called with data:', projectData);
}
const newProject = await prisma.project.create({
data: { ...projectData, userId },
include: { files: true },
});
return newProject;
}
export async function update(projectId, userId, updatedData) {
if (process.env.NODE_ENV === 'development') {
console.log(
'projectsRepo.update() called for projectId:',
projectId,
'with data:',
updatedData
);
}
try {
const project = await prisma.project.findUnique({
where: { id: projectId },
});
if (!project || project.userId !== userId) {
return null;
}
const updatedProject = await prisma.project.update({
where: { id: projectId },
data: updatedData,
include: { files: true },
});
return updatedProject;
} catch (error) {
if (error.code === 'P2025') return null;
throw error;
}
}
export async function remove(projectId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log('projectsRepo.remove() called for projectId:', projectId);
}
try {
const project = await prisma.project.findUnique({
where: { id: projectId },
});
if (!project || project.userId !== userId) {
return null;
}
const deletedProject = await prisma.project.delete({
where: { id: projectId },
include: { files: true },
});
return deletedProject;
} catch (error) {
if (error.code === 'P2025') return null;
throw error;
}
}
export async function existsByName(userId, name) {
if (process.env.NODE_ENV === 'development') {
console.log('projectsRepo.existsByName() called for name:', name);
}
const project = await prisma.project.findFirst({
where: { userId, name },
});
return project !== null;
}
export async function getFilesByProjectId(projectId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log(
'projectsRepo.getFilesByProjectId() called for projectId:',
projectId,
'userId:',
userId
);
}
try {
const project = await prisma.project.findUnique({
where: { id: projectId },
});
if (!project || project.userId !== userId) {
return null;
}
const files = await prisma.file.findMany({
where: { projectId },
});
return files;
} catch (error) {
throw error;
}
}
export async function addFile(projectId, userId, fileData) {
if (process.env.NODE_ENV === 'development') {
console.log(
'projectsRepo.addFile() called for projectId:',
projectId,
'userId:',
userId,
'with data:',
fileData
);
}
const project = await prisma.project.findUnique({
where: { id: projectId },
});
if (!project || project.userId !== userId) {
return null;
}
const newFile = await prisma.file.create({
data: {
name: fileData.name,
file: fileData.file,
size: fileData.size,
mimeType: fileData.mimeType,
projectId: projectId
}
});
return newFile;
}
export async function deleteFile(fileId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log('projectsRepo.deleteFile() called for fileId:', fileId, 'userId:', userId);
}
const file = await prisma.file.findUnique({
where: { id: fileId },
include: { project: true }
});
if (!file || file.project.userId !== userId) {
return null;
}
return await prisma.file.delete({
where: { id: fileId },
});
}
export async function getFilesWithContent(projectId, userId) {
const project = await prisma.project.findUnique({
where: { id: projectId },
});
if (!project || project.userId !== userId) return null;
return await prisma.file.findMany({
where: { projectId },
select: {
id: true,
name: true,
file: true,
size: true,
mimeType: true,
projectId: true
}
});
}

View File

@@ -0,0 +1,30 @@
import prisma from '../config/db.js';
export async function createUser(data) {
if (process.env.NODE_ENV === 'development') {
console.log('userRepo.createUser() called for username:', data.username);
}
try {
const newUser = await prisma.user.create({
data,
omit: { password: true },
});
return newUser;
} catch (error) {
if (error.code === 'P2002') {
const err = new Error('Username already exists');
err.status = 409;
throw err;
}
throw error;
}
}
export async function findUserByUsername(username) {
if (process.env.NODE_ENV === 'development') {
console.log('userRepo.findUserByUsername() called for username:', username);
}
return await prisma.user.findUnique({ where: { username } });
}

10
src/routes/authRoutes.js Normal file
View File

@@ -0,0 +1,10 @@
import express from 'express';
import { logInHandler, signUpHandler } from '../controllers/authController.js';
import { validateSignUp, validateLogIn } from '../middleware/validateAuth.js';
const router = express.Router();
router.post('/signup', validateSignUp, signUpHandler);
router.post('/login', validateLogIn, logInHandler);
export default router;

25
src/routes/ideasRoutes.js Normal file
View File

@@ -0,0 +1,25 @@
import express from 'express';
import {
getAllIdeasHandler,
createIdeaHandler,
updateIdeaHandler,
deleteIdeaHandler,
} from '../controllers/ideasController.js';
import {
validateGetAllQuery,
validateId,
validateCreateIdea,
validateUpdateIdea,
} from '../middleware/validateIdeas.js';
import { authenticate } from '../middleware/authenticate.js';
const router = express.Router();
router.use(authenticate);
router.get('/', validateGetAllQuery, getAllIdeasHandler);
router.post('/', validateCreateIdea, createIdeaHandler);
router.put('/:id', validateId, validateUpdateIdea, updateIdeaHandler);
router.delete('/:id', validateId, deleteIdeaHandler);
export default router;

View File

@@ -0,0 +1,32 @@
import express from 'express';
import {
getAllMaterialsHandler,
getMaterialsByProjectHandler,
getMaterialByIdHandler,
createMaterialHandler,
deleteMaterialHandler,
} from '../controllers/materialsController.js';
import {
validateGetAllQuery,
validateId,
validateProjectId,
validateCreateMaterial,
} from '../middleware/validateMaterials.js';
import { authenticate } from '../middleware/authenticate.js';
const router = express.Router();
router.use(authenticate);
router.get('/', validateGetAllQuery, getAllMaterialsHandler);
router.get(
'/project/:projectId',
validateProjectId,
validateGetAllQuery,
getMaterialsByProjectHandler
);
router.get('/:id', validateId, getMaterialByIdHandler);
router.post('/', validateCreateMaterial, createMaterialHandler);
router.delete('/:id', validateId, deleteMaterialHandler);
export default router;

View File

@@ -0,0 +1,44 @@
import express from 'express';
import multer from 'multer';
import {
getAllProjectsHandler,
getProjectByIdHandler,
createProjectHandler,
updateProjectHandler,
deleteProjectHandler,
getProjectFilesHandler,
addProjectFileHandler,
deleteProjectFileHandler,
downloadProjectFilesHandler
} from '../controllers/projectsController.js';
import {
validateGetAllQuery,
validateId,
validateCreateProject,
validateUpdateProject,
} from '../middleware/validateProjects.js';
import { authenticate } from '../middleware/authenticate.js';
const router = express.Router();
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 10 * 1024 * 1024 }
});
router.use(authenticate);
//Project Routes
router.get('/', validateGetAllQuery, getAllProjectsHandler);
router.get('/:id', validateId, getProjectByIdHandler);
router.post('/', validateCreateProject, createProjectHandler);
router.put('/:id', validateId, validateUpdateProject, updateProjectHandler);
router.delete('/:id', validateId, deleteProjectHandler);
// Project File Routes
router.get('/:id/files', validateId, getProjectFilesHandler);
router.post('/:id/files', validateId, upload.single('file'), addProjectFileHandler);
router.delete('/files/:id', validateId, deleteProjectFileHandler);
router.get('/:id/files/download', validateId, downloadProjectFilesHandler);
export default router;

57
src/server.js Normal file
View File

@@ -0,0 +1,57 @@
import express from 'express';
import morgan from 'morgan';
import fs from 'fs';
import swaggerUI from 'swagger-ui-express';
import yaml from 'js-yaml';
import authRoutes from './routes/authRoutes.js';
import ideasRoutes from './routes/ideasRoutes.js';
import projectsRoutes from './routes/projectsRoutes.js';
import materialsRoutes from './routes/materialsRoutes.js';
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
if (process.env.NODE_ENV !== 'test') app.use(morgan('tiny'));
let specs;
try {
specs = yaml.load(fs.readFileSync('./docs/openapi.yaml', 'utf8'));
} catch (error) {
console.log('Failed to load OpenAPI specification', error);
process.exit(1);
}
app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(specs));
app.use('/api/auth', authRoutes);
app.use('/api/ideas', ideasRoutes);
app.use('/api/projects', projectsRoutes);
app.use('/api/materials', materialsRoutes);
app.use((req, res, next) => {
const err = new Error('Not Found');
err.status = 404;
next(err);
});
app.use((err, req, res, next) => {
if (err.code === "LIMIT_FILE_SIZE") {
return res.status(413).json({
error: 'File size cannot exceed 10MB'
});
}
console.log(err.stack);
if (!err.status) {
err.status = 500;
err.message = 'Internal Server Error';
}
res.status(err.status).json({ error: err.message });
});
if (process.env.NODE_ENV !== 'test') {
app.listen(PORT, () => console.log(`Server is running on port ${PORT}`));
}
export default app;

View File

@@ -0,0 +1,36 @@
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { createUser, findUserByUsername } from '../repositories/userRepo.js';
export async function signUp(username, password) {
if (process.env.NODE_ENV === 'development') {
console.log('authService.signUp() called for username:', username);
}
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = await createUser({ username, password: hashedPassword });
return newUser;
}
export async function logIn(username, password) {
if (process.env.NODE_ENV === 'development') {
console.log('authService.logIn() called for username:', username);
}
const JWT_SECRET = process.env.JWT_SECRET;
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN;
const user = await findUserByUsername(username);
const error = new Error('Invalid credentials');
error.status = 401;
if (!user) throw error;
const match = await bcrypt.compare(password, user.password);
if (!match) throw error;
const accessToken = jwt.sign({ id: user.id }, JWT_SECRET, {
expiresIn: JWT_EXPIRES_IN,
});
return accessToken;
}

View File

@@ -0,0 +1,88 @@
import {
getAll,
getById,
create,
update,
remove,
existsByName,
} from '../repositories/ideasRepo.js';
export async function getAllIdeas(userId, options = {}) {
if (process.env.NODE_ENV === 'development') {
console.log('ideasService.getAllIdeas() called for userId:', userId);
}
const ideas = await getAll(userId, options);
return ideas;
}
export async function getIdeaById(ideaId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log('ideasService.getIdeaById() called for ideaId:', ideaId);
}
const idea = await getById(ideaId, userId);
if (idea) return idea;
const error = new Error(`Idea ${ideaId} not found`);
error.status = 404;
throw error;
}
export async function createIdea(userId, ideaData) {
if (process.env.NODE_ENV === 'development') {
console.log('ideasService.createIdea() called with data:', ideaData);
}
const ideaExists = await existsByName(userId, ideaData.name);
if (ideaExists) {
const error = new Error(`Idea with name "${ideaData.name}" already exists`);
error.status = 409;
throw error;
}
const newIdea = await create(userId, ideaData);
return newIdea;
}
export async function updateIdea(ideaId, userId, updatedData) {
if (process.env.NODE_ENV === 'development') {
console.log(
'ideasService.updateIdea() called for ideaId:',
ideaId,
'with data:',
updatedData
);
}
if (updatedData.name) {
const ideaExists = await existsByName(userId, updatedData.name);
if (ideaExists) {
const error = new Error(
`Idea with name "${updatedData.name}" already exists`
);
error.status = 409;
throw error;
}
}
const updatedIdea = await update(ideaId, userId, updatedData);
if (updatedIdea) return updatedIdea;
const error = new Error(`Idea ${ideaId} not found`);
error.status = 404;
throw error;
}
export async function deleteIdea(ideaId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log('ideasService.deleteIdea() called for ideaId:', ideaId);
}
const result = await remove(ideaId, userId);
if (result) return;
const error = new Error(`Idea ${ideaId} not found`);
error.status = 404;
throw error;
}

View File

@@ -0,0 +1,85 @@
import {
getAll,
getByProjectId,
getById,
create,
remove,
existsByName,
} from '../repositories/materialsRepo.js';
export async function getAllMaterials(userId, options = {}) {
if (process.env.NODE_ENV === 'development') {
console.log(
'materialsService.getAllMaterials() called for userId:',
userId
);
}
const materials = await getAll(userId, options);
return materials;
}
export async function getMaterialsByProject(projectId, userId, options = {}) {
if (process.env.NODE_ENV === 'development') {
console.log(
'materialsService.getMaterialsByProject() called for projectId:',
projectId
);
}
const materials = await getByProjectId(projectId, userId, options);
return materials;
}
export async function getMaterialById(materialId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log(
'materialsService.getMaterialById() called for materialId:',
materialId
);
}
const material = await getById(materialId, userId);
if (material) return material;
const error = new Error(`Material ${materialId} not found`);
error.status = 404;
throw error;
}
export async function createMaterial(userId, materialData) {
if (process.env.NODE_ENV === 'development') {
console.log(
'materialsService.createMaterial() called with data:',
materialData
);
}
const materialExists = await existsByName(userId, materialData.name);
if (materialExists) {
const error = new Error(
`Material with name "${materialData.name}" already exists`
);
error.status = 409;
throw error;
}
const newMaterial = await create(userId, materialData);
return newMaterial;
}
export async function deleteMaterial(materialId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log(
'materialsService.deleteMaterial() called for materialId:',
materialId
);
}
const result = await remove(materialId, userId);
if (result) return;
const error = new Error(`Material ${materialId} not found`);
error.status = 404;
throw error;
}

View File

@@ -0,0 +1,155 @@
import {
getAll,
getById,
create,
update,
remove,
existsByName,
getFilesByProjectId,
getFilesWithContent,
addFile,
deleteFile
} from '../repositories/projectsRepo.js';
export async function getAllProjects(userId, options = {}) {
if (process.env.NODE_ENV === 'development') {
console.log('projectsService.getAllProjects() called for userId:', userId);
}
return await getAll(userId, options);
}
export async function getProjectById(projectId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log(
'projectsService.getProjectById() called for projectId:',
projectId
);
}
const project = await getById(projectId, userId);
if (project) return project;
const error = new Error(`Project ${projectId} not found`);
error.status = 404;
throw error;
}
export async function createProject(userId, projectData) {
if (process.env.NODE_ENV === 'development') {
console.log(
'projectsService.createProject() called with data:',
projectData
);
}
const projectExists = await existsByName(userId, projectData.name);
if (projectExists) {
const error = new Error(
`Project with name "${projectData.name}" already exists`
);
error.status = 409;
throw error;
}
const newProject = await create(userId, projectData);
return newProject;
}
export async function updateProject(projectId, userId, updatedData) {
if (process.env.NODE_ENV === 'development') {
console.log(
'projectsService.updateProject() called for projectId:',
projectId,
'with data:',
updatedData
);
}
if (updatedData.name) {
const projectExists = await existsByName(userId, updatedData.name);
if (projectExists) {
const error = new Error(
`Project with name "${updatedData.name}" already exists`
);
error.status = 409;
throw error;
}
}
const updatedProject = await update(projectId, userId, updatedData);
if (updatedProject) return updatedProject;
const error = new Error(`Project ${projectId} not found`);
error.status = 404;
throw error;
}
export async function deleteProject(projectId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log(
'projectsService.deleteProject() called for projectId:',
projectId
);
}
const result = await remove(projectId, userId);
if (result) return;
const error = new Error(`Project ${projectId} not found`);
error.status = 404;
throw error;
}
export async function getProjectFilesById(projectId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log(
'projectsService.getProjectFilesById() called for projectId:',
projectId
);
}
const files = await getFilesByProjectId(projectId, userId);
if (files) return files;
const error = new Error(`Project ${projectId} not found`);
error.status = 404;
throw error;
}
export async function getProjectFilesForDownload(projectId, userId) {
const files = await getFilesWithContent(projectId, userId);
if (!files) {
const error = new Error(`Project ${projectId} not found or unauthorized`);
error.status = 404;
throw error;
}
return files;
}
export async function addProjectFile(projectId, userId, fileData) {
if (process.env.NODE_ENV === 'development') {
console.log(
'projectsService.addProjectFile() called for projectId:',
projectId,
'with fileData:',
fileData
);
}
const newFile = await addFile(projectId, userId, fileData);
if (!newFile) {
const error = new Error(`Project ${projectId} not found or unauthorized`);
error.status = 404;
throw error;
}
return newFile;
}
export async function deleteProjectFile(fileId, userId) {
if (process.env.NODE_ENV === 'development') {
console.log('projectsService.deleteProjectFile() called for fileId:', fileId);
}
const deletedFile = await deleteFile(fileId, userId);
if (deletedFile) return deletedFile;
}