-
Notifications
You must be signed in to change notification settings - Fork 3
Implemented a dashboard for managing sheets #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e9d5de9
f4fe220
f727b59
0433c7b
a4111a8
1462605
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # Generated by Django 6.0.2 on 2026-04-21 19:29 | ||
|
|
||
| import django.db.models.deletion | ||
| from django.conf import settings | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ('api', '0002_cheatsheet_selected_formulas'), | ||
| migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name='cheatsheet', | ||
| name='user', | ||
| field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cheat_sheets', to=settings.AUTH_USER_MODEL), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # Generated by Django 6.0.4 on 2026-04-22 17:58 | ||
|
|
||
| import django.db.models.deletion | ||
| from django.conf import settings | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ('api', '0003_cheatsheet_user'), | ||
| migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AlterField( | ||
| model_name='cheatsheet', | ||
| name='user', | ||
| field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cheat_sheets', to=settings.AUTH_USER_MODEL), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,10 @@ | ||
| from rest_framework.decorators import api_view, action | ||
| from rest_framework.decorators import api_view, action, permission_classes | ||
| from rest_framework.response import Response | ||
| from rest_framework import status, viewsets | ||
| from django.http import FileResponse | ||
| from django.contrib.auth.models import User | ||
| from rest_framework.generics import CreateAPIView | ||
| from rest_framework.permissions import AllowAny | ||
| from rest_framework.permissions import AllowAny, IsAuthenticated | ||
| from django.shortcuts import get_object_or_404 | ||
| import subprocess | ||
| import tempfile | ||
|
|
@@ -129,6 +129,7 @@ def generate_sheet(request): | |
|
|
||
|
|
||
| @api_view(["POST"]) | ||
| @permission_classes([IsAuthenticated]) | ||
| def compile_latex(request): | ||
| """ | ||
| POST /api/compile/ | ||
|
|
@@ -142,7 +143,7 @@ def compile_latex(request): | |
|
|
||
| # If cheat_sheet_id is provided, get content from the cheat sheet | ||
| if cheat_sheet_id: | ||
| cheatsheet = get_object_or_404(CheatSheet, pk=cheat_sheet_id) | ||
| cheatsheet = get_object_or_404(CheatSheet, pk=cheat_sheet_id, user=request.user) | ||
| content = cheatsheet.build_full_latex() | ||
|
|
||
| if not content: | ||
|
|
@@ -209,6 +210,13 @@ class CheatSheetViewSet(viewsets.ModelViewSet): | |
| """ | ||
| queryset = CheatSheet.objects.all() | ||
| serializer_class = CheatSheetSerializer | ||
| permission_classes = [IsAuthenticated] | ||
|
|
||
| def get_queryset(self): | ||
| return self.queryset.filter(user=self.request.user).order_by('-updated_at') | ||
|
Comment on lines
+213
to
+216
|
||
|
|
||
| def perform_create(self, serializer): | ||
| serializer.save(user=self.request.user) | ||
|
|
||
| @action(detail=False, methods=['post'], url_path='from-template') | ||
| def from_template(self, request): | ||
|
|
@@ -226,6 +234,7 @@ def from_template(self, request): | |
|
|
||
| cheatsheet = CheatSheet.objects.create( | ||
| title=title, | ||
| user=request.user, | ||
| template=template, | ||
| latex_content=template.latex_content, | ||
| margins=template.default_margins, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ import { Routes, Route, Link, Navigate } from 'react-router-dom'; | |
| import AuthContext from './context/AuthContext'; | ||
| import Login from './components/Login'; | ||
| import SignUp from './components/SignUp'; | ||
| import Dashboard from './components/Dashboard'; | ||
| import './App.css' | ||
| import CreateCheatSheet from './components/CreateCheatSheet'; | ||
|
|
||
|
|
@@ -48,7 +49,7 @@ function App() { | |
| localStorage.setItem('theme', theme); | ||
| }, [theme]); | ||
|
|
||
| const { user, logoutUser } = useContext(AuthContext); | ||
| const { user, authTokens, logoutUser } = useContext(AuthContext); | ||
|
|
||
| const toggleTheme = () => { | ||
| setTheme(prev => prev === 'dark' ? 'light' : 'dark'); | ||
|
|
@@ -57,6 +58,8 @@ function App() { | |
| const handleReset = () => { | ||
| setCheatSheet(DEFAULT_SHEET); | ||
| localStorage.setItem('currentCheatSheet', JSON.stringify(DEFAULT_SHEET)); | ||
| localStorage.removeItem('cheatSheetData'); | ||
| localStorage.removeItem('cheatSheetLatex'); | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
|
|
@@ -90,7 +93,10 @@ function App() { | |
| const sheetId = nextSheet.id; | ||
| const response = await fetch(sheetId ? `/api/cheatsheets/${sheetId}/` : '/api/cheatsheets/', { | ||
| method: sheetId ? 'PATCH' : 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| ...(authTokens?.access ? { 'Authorization': `Bearer ${authTokens.access}` } : {}), | ||
| }, | ||
|
Comment on lines
94
to
+99
|
||
| body: JSON.stringify({ | ||
| title: nextSheet.title, | ||
| latex_content: nextSheet.content, | ||
|
|
@@ -128,6 +134,22 @@ function App() { | |
| } | ||
| }; | ||
|
|
||
| const handleEditSheet = (sheet) => { | ||
| const editSheet = { | ||
| id: sheet.id, | ||
| title: sheet.title, | ||
| content: sheet.latex_content, | ||
| columns: sheet.columns, | ||
| margins: sheet.margins, | ||
| fontSize: sheet.font_size, | ||
| selectedFormulas: sheet.selected_formulas || [], | ||
| }; | ||
| setCheatSheet(editSheet); | ||
| localStorage.setItem('currentCheatSheet', JSON.stringify(editSheet)); | ||
| localStorage.removeItem('cheatSheetData'); | ||
| localStorage.removeItem('cheatSheetLatex'); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="App"> | ||
| <header className="app-header"> | ||
|
|
@@ -142,6 +164,8 @@ function App() { | |
| {user ? ( | ||
| <> | ||
| <span style={{ fontWeight: 'bold' }}>Hi, {user.username || 'User'}</span> | ||
| <Link to="/" className="btn primary" onClick={handleReset}>Create New Sheet</Link> | ||
| <Link to="/dashboard" className="btn">My Sheets</Link> | ||
| <button onClick={logoutUser} className="btn">Log Out</button> | ||
| </> | ||
| ) : ( | ||
|
|
@@ -169,6 +193,11 @@ function App() { | |
| /> | ||
| </PrivateRoute> | ||
| } /> | ||
| <Route path="/dashboard" element={ | ||
| <PrivateRoute> | ||
| <Dashboard onEditSheet={handleEditSheet} onCreateNewSheet={handleReset} /> | ||
| </PrivateRoute> | ||
| } /> | ||
| <Route path="/login" element={<Login />} /> | ||
| <Route path="/signup" element={<SignUp />} /> | ||
| </Routes> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While adding
IsAuthenticatedhere is good,compile_latexstill resolvescheat_sheet_idlater viaget_object_or_404(CheatSheet, pk=cheat_sheet_id)without checking that the sheet belongs torequest.user. That creates an IDOR risk (authenticated users can compile other users' sheets if they guess IDs). When acheat_sheet_idis provided, ensure the lookup is restricted touser=request.user(or enforce object-level permissions) and return 404/403 for non-owned sheets.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot apply changes based on this feedback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already addressed in commit
1462605.compile_latexnow usesget_object_or_404(CheatSheet, pk=cheat_sheet_id, user=request.user), returning 404 for any sheet not owned by the requesting user.