itamarlifshitz commited on
Commit
69a00c8
·
verified ·
1 Parent(s): 4732481

/* DeepStudy Pro MAX — Luxe UI * Same powerhouse features, premium UI polish for DeepSite. * Env: OPENAI_API_KEY, OPENAI_MODEL, JWT_SECRET, SMTP_*, APP_BASE_URL */ require('dotenv').config(); const path=require('path'),fs=require('fs'),os=require('os'); const express=require('express'),cors=require('cors'),bodyParser=require('body-parser'); const multer=require('multer'),pdfParse=require('pdf-parse'),mammoth=require('mammoth'); const {v4:uuidv4}=require('uuid'); const helmet=require('helmet'); const rateLimit=require('express-rate-limit'); const cookieParser=require('cookie-parser'); const nodemailer=require('nodemailer'); const bcrypt=require('bcryptjs'); const jwt=require('jsonwebtoken'); const OpenAI=require('openai'); const app=express(); app.use(helmet({contentSecurityPolicy:false})); app.use(cors({origin:true,credentials:true})); app.use(cookieParser()); app.use(bodyParser.json({limit:'16mb'})); app.use(rateLimit({windowMs:60_000,max:180})); const PORT=process.env.PORT||3000; const APP_BASE_URL=process.env.APP_BASE_URL||`http://localhost:${PORT}`; const JWT_SECRET=process.env.JWT_SECRET||'dev_change_me'; const DATA_DIR=path.join(process.cwd(),'data'); const USERS_PATH=path.join(DATA_DIR,'users.json'); const PACKS_PATH=path.join(DATA_DIR,'packs.json'); if(!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR); if(!fs.existsSync(USERS_PATH)) fs.writeFileSync(USERS_PATH,JSON.stringify([])); if(!fs.existsSync(PACKS_PATH)) fs.writeFileSync(PACKS_PATH,JSON.stringify([])); const readJSON=p=>{try{return JSON.parse(fs.readFileSync(p,'utf8'));}catch{return[];}}; const writeJSON=(p,v)=>fs.writeFileSync(p,JSON.stringify(v,null,2)); const readUsers=()=>readJSON(USERS_PATH), writeUsers=a=>writeJSON(USERS_PATH,a); const readPacks=()=>readJSON(PACKS_PATH), writePacks=a=>writeJSON(PACKS_PATH,a); let transporter=null; if(process.env.SMTP_HOST){ transporter=nodemailer.createTransport({host:process.env.SMTP_HOST,port:Number(process.env.SMTP_PORT||587),secure:false,auth:{user:process.env.SMTP_USER,pass:process.env.SMTP_PASS}}); } const openaiConfigured=!!process.env.OPENAI_API_KEY; const openaiClient=openaiConfigured? new OpenAI.OpenAI({apiKey:process.env.OPENAI_API_KEY}):null; const MODEL=process.env.OPENAI_MODEL||'gpt-4o-mini'; const sanitize=s=>(s||'').toString().replace(/\u0000/g,'').trim(); const safeParseJSON=s=>{try{return JSON.parse(s);}catch{return null;}}; const signToken=(payload,exp='7d')=>jwt.sign(payload,JWT_SECRET,{expiresIn:exp}); const verifyToken=t=>{try{return jwt.verify(t,JWT_SECRET);}catch{return null;}}; function authRequired(req,res,next){const t=req.cookies?.dsp_auth; if(!t) return res.status(401).json({ok:false,error:'נדרשת התחברות'}); const dec=verifyToken(t); if(!dec) return res.status(401).json({ok:false,error:'אסימון לא תקף'}); req.user=dec; next();} const upload=multer({dest:path.join(os.tmpdir(),'deepstudy_uploads'),limits:{fileSize:32*1024*1024}}); async function extractText(filePath,originalName){ const ext=(path.extname(originalName||'').toLowerCase()||'').slice(1); if(ext==='pdf'){const d=await pdfParse(fs.readFileSync(filePath)); return sanitize(d.text);} if(ext==='docx'){const {value}=await mammoth.extractRawText({path:filePath}); return sanitize(value);} if(ext==='txt'||ext===''){return sanitize(fs.readFileSync(filePath,'utf8'));} throw new Error(`פורמט לא נתמך: ${ext} (pdf/docx/txt)`); } function chunkText(str,targetTokens=1200,overlapTokens=120){ const txt=sanitize(str); const charsPerTok=3; const chunkChars=targetTokens*charsPerTok; const overlapChars=overlapTokens*charsPerTok; const out=[]; let i=0,idx=1; while(i

Browse files

=txt.length) break;} return out; } async function llm(messages,system){ if(!openaiConfigured){const last=messages[messages.length-1]?.content||''; return `🔁 מצב דמו:\n${last.slice(0,600)}\n\n[הדגמה]`;} const r=await openaiClient.chat.completions.create({model:MODEL,temperature:0.35,messages:[{role:'system',content:system||'You are a kind Hebrew school tutor.'},...messages]}); return r.choices?.[0]?.message?.content?.trim()||''; } const mapPrompt=(goal,sid,text)=>[{role:'user',content: `מקטע "${sid}". מטרה: ${sanitize(goal)||'הבנה + הכנה לבוחן'}. """ ${text} """ צור תקציר (5–8 משפטים), נקודות מפתח, ומושגים. Markdown: ## ${sid} ### תקציר - ... ### נקודות מפתח - ... ### מושגים - מושג | הסבר קצר`}]; function reducePrompt(goal,mapped,merged){return[{role:'user',content: `אחד את סיכומי המקטעים ל"חבילת לימוד" בעברית עם [Si]. מטרה: ${sanitize(goal)||'הבנה + תרגול'}. סיכומים: """ ${mapped} """ (להקשר בלבד): """ ${merged.slice(0,20000)} """ הפק Markdown: # סיכום (8–12 משפטים, עם [Si]) # נקודות מפתח (בולטים + [Si]) # מילון מושגים (טבלה: מושג | הסבר קצר | מקטעים) # כרטיסיות (8–14): שאלה → תשובה קצרה # מבחן לדוגמה (10–14) בסוף הוסף JSON תקין: {"answerKey":[{"qid":"Q1","correct":"B","explanation":"..."}, ...]}`}];} async function buildStudyPack(goal,merged){ const chunks=chunkText(merged,1200,120); if(!chunks.length){return{studyPackMarkdown:'# סיכום\nדמו קצר.',answerKey:{answerKey:[]},sections:[]};} const mapped=[]; for(const ch of chunks) mapped.push(await llm(mapPrompt(goal,ch.id,ch.text))); const mappedMarkdown=mapped.join('\n\n'); const reduced=await llm(reducePrompt(goal,mappedMarkdown,merged)); let answerKey={answerKey:[]}; const m=reduced.match(/\{[\s\S]*\}/g)||[]; for(let i=m.length-1;i>=0;i--){const c=safeParseJSON(m[i]); if(c?.answerKey){answerKey=c;break;}} return{studyPackMarkdown:reduced,answerKey,sections:chunks.map(c=>c.id)}; } // ---------- AUTH ---------- app.post('/api/auth/signup',async(req,res)=>{ try{ const email=sanitize(req.body.email).toLowerCase(), pw=sanitize(req.body.password); if(!email||!pw||pw.length<6) return res.status(400).json({ok:false,error:'אימייל/סיסמה לא תקינים'}); const users=readUsers(); if(users.find(u=>u.email===email)) return res.status(409).json({ok:false,error:'אימייל קיים'}); const hash=await bcrypt.hash(pw,10); const user={id:uuidv4(),email,passwordHash:hash,createdAt:Date.now(),emailVerified:false}; users.push(user); writeUsers(users); if(transporter){ const t=signToken({action:'verify',uid:user.id},'2d'); const link=`${APP_BASE_URL}/verify?token=${t}`; await transporter.sendMail({from:process.env.SMTP_FROM||'DeepStudy Pro ',to:email,subject:'ברוכה הבאה! אשרי את המייל 🎉', html:`
ברוכה הבאה
לאימות החשבון:

לחצי כאן

`}); } const token=signToken({uid:user.id,email:user.email}); res.cookie('dsp_auth',token,{httpOnly:true,sameSite:'lax',maxAge:7*24*3600*1000}); res.json({ok:true,user:{id:user.id,email:user.email,emailVerified:user.emailVerified},info:transporter?'נשלח מייל אימות':'דמו: אין SMTP'}); }catch(e){console.error(e);res.status(500).json({ok:false,error:'שגיאה בהרשמה'});} }); app.post('/api/auth/login',async(req,res)=>{ try{ const email=sanitize(req.body.email).toLowerCase(), pw=sanitize(req.body.password); const users=readUsers(); const u=users.find(x=>x.email===email); if(!u) return res.status(401).json({ok:false,error:'שגוי'}); const ok=await bcrypt.compare(pw,u.passwordHash); if(!ok) return res.status(401).json({ok:false,error:'שגוי'}); const token=signToken({uid:u.id,email:u.email}); res.cookie('dsp_auth',token,{httpOnly:true,sameSite:'lax',maxAge:7*24*3600*1000}); res.json({ok:true,user:{id:u.id,email:u.email,emailVerified:u.emailVerified}}); }catch(e){console.error(e);res.status(500).json({ok:false,error:'שגיאה בהתחברות'});} }); app.post('/api/auth/logout',(req,res)=>{res.clearCookie('dsp_auth');res.json({ok:true});}); app.get('/api/auth/me',(req,res)=>{const t=req.cookies?.dsp_auth; const dec=t&&verifyToken(t); if(!dec) return res.json({ok:false}); const u=readUsers().find(x=>x.id===dec.uid); if(!u) return res.json({ok:false}); res.json({ok:true,user:{id:u.id,email:u.email,emailVerified:u.emailVerified}}); }); app.get('/api/auth/verify',(req,res)=>{const t=sanitize(req.query.token); const d=verifyToken(t); if(!d||d.action!=='verify') return res.status(400).json({ok:false,error:'טוקן לא תקף'}); const users=readUsers(); const u=users.find(x=>x.id===d.uid); if(!u) return res.status(400).json({ok:false,error:'לא קיים'}); if(!u.emailVerified){u.emailVerified=true; writeUsers(users);} res.json({ok:true,verified:true}); }); app.post('/api/auth/request-reset',async(req,res)=>{const email=sanitize(req.body.email).toLowerCase(); const u=readUsers().find(x=>x.email===email); if(u&&transporter){const t=signToken({action:'reset',uid:u.id},'2h'); const link=`${APP_BASE_URL}/reset?token=${t}`; await transporter.sendMail({from:process.env.SMTP_FROM||'DeepStudy Pro ',to:email,subject:'איפוס סיסמה',html:`
איפוס
קישור לאיפוס
`});} res.json({ok:true}); }); app.post('/api/auth/reset',async(req,res)=>{const {token,password}=req.body||{}; const d=verifyToken(sanitize(token)); if(!d||d.action!=='reset') return res.status(400).json({ok:false,error:'טוקן לא תקף'}); if(!password||String(password).length<6) return res.status(400).json({ok:false,error:'סיסמה קצרה'}); const users=readUsers(); const u=users.find(x=>x.id===d.uid); if(!u) return res.status(400).json({ok:false,error:'לא נמצא'}); u.passwordHash=await bcrypt.hash(String(password),10); writeUsers(users); res.json({ok:true}); }); // ---------- CORE ---------- app.get('/api/health',(_req,res)=>res.json({ok:true,model:openaiConfigured?MODEL:'DEMO',smtp:!!transporter})); app.post('/api/study-pack',authRequired,upload.array('files',6),async(req,res)=>{ const cleanup=()=> (req.files||[]).forEach(f=>fs.existsSync(f.path)&&fs.unlinkSync(f.path)); try{ const goal=sanitize(req.body.goal).slice(0,600); let merged=''; if(req.files?.length){for(const f of req.files){merged+=`\n\n===== ${f.originalname} =====\n${await extractText(f.path,f.originalname)}\n`;}} const raw=sanitize(req.body.text||''); if(raw) merged+=`\n\n===== Pasted Text =

Files changed (2) hide show
  1. routes/auth.js +69 -0
  2. utils/helpers.js +36 -0
routes/auth.js ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { readUsers, writeUsers, signToken, verifyToken } = require('../utils/helpers');
4
+ const bcrypt = require('bcryptjs');
5
+ const { v4: uuidv4 } = require('uuid');
6
+
7
+ // Signup route
8
+ router.post('/signup', async (req, res) => {
9
+ try {
10
+ const email = sanitize(req.body.email).toLowerCase();
11
+ const pw = sanitize(req.body.password);
12
+
13
+ if (!email || !pw || pw.length < 6) {
14
+ return res.status(400).json({ ok: false, error: 'אימייל/סיסמה לא תקינים' });
15
+ }
16
+
17
+ const users = readUsers();
18
+ if (users.find(u => u.email === email)) {
19
+ return res.status(409).json({ ok: false, error: 'אימייל קיים' });
20
+ }
21
+
22
+ const hash = await bcrypt.hash(pw, 10);
23
+ const user = {
24
+ id: uuidv4(),
25
+ email,
26
+ passwordHash: hash,
27
+ createdAt: Date.now(),
28
+ emailVerified: false
29
+ };
30
+
31
+ users.push(user);
32
+ writeUsers(users);
33
+
34
+ if (transporter) {
35
+ const t = signToken({ action: 'verify', uid: user.id }, '2d');
36
+ const link = `${APP_BASE_URL}/verify?token=${t}`;
37
+ await transporter.sendMail({
38
+ from: process.env.SMTP_FROM || 'DeepStudy Pro <noreply@example.com>',
39
+ to: email,
40
+ subject: 'ברוכה הבאה! אשרי את המייל 🎉',
41
+ html: `...email template...`
42
+ });
43
+ }
44
+
45
+ const token = signToken({ uid: user.id, email: user.email });
46
+ res.cookie('dsp_auth', token, {
47
+ httpOnly: true,
48
+ sameSite: 'lax',
49
+ maxAge: 7 * 24 * 3600 * 1000
50
+ });
51
+
52
+ res.json({
53
+ ok: true,
54
+ user: {
55
+ id: user.id,
56
+ email: user.email,
57
+ emailVerified: user.emailVerified
58
+ },
59
+ info: transporter ? 'נשלח מייל אימות' : 'דמו: אין SMTP'
60
+ });
61
+ } catch (e) {
62
+ console.error(e);
63
+ res.status(500).json({ ok: false, error: 'שגיאה בהרשמה' });
64
+ }
65
+ });
66
+
67
+ // ... other auth routes ...
68
+
69
+ module.exports = router;
utils/helpers.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ // Data storage helper functions
5
+ const readJSON = (p) => {
6
+ try {
7
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
8
+ } catch {
9
+ return [];
10
+ }
11
+ };
12
+
13
+ const writeJSON = (p, v) => fs.writeFileSync(p, JSON.stringify(v, null, 2));
14
+
15
+ // Text sanitization
16
+ const sanitize = (s) => (s || '').toString().replace(/\u0000/g, '').trim();
17
+
18
+ // JSON parsing with safety
19
+ const safeParseJSON = (s) => {
20
+ try {
21
+ return JSON.parse(s);
22
+ } catch {
23
+ return null;
24
+ }
25
+ };
26
+
27
+ module.exports = {
28
+ readJSON,
29
+ writeJSON,
30
+ sanitize,
31
+ safeParseJSON,
32
+ readUsers: () => readJSON(path.join(process.cwd(), 'data', 'users.json')),
33
+ writeUsers: (a) => writeJSON(path.join(process.cwd(), 'data', 'users.json'), a),
34
+ readPacks: () => readJSON(path.join(process.cwd(), 'data', 'packs.json')),
35
+ writePacks: (a) => writeJSON(path.join(process.cwd(), 'data', 'packs.json'), a)
36
+ };