diff --git a/.gitignore b/.gitignore index 8e0cfe12..c5cdb601 100644 --- a/.gitignore +++ b/.gitignore @@ -170,3 +170,6 @@ dmypy.json # static images mondey_backend/static/*.jpg + +# ssl keys +*.pem diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 3ec346c9..6724905f 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -25,6 +25,12 @@ sudo docker compose ps sudo docker compose logs ``` +To update the running website to the latest version: + +``` +sudo docker compose pull && sudo docker compose up -d && sudo docker system prune -af +``` + ### Give users admin rights To make an existing user with email `user@domain.com` into an admin, modify the users database, e.g. diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 00000000..016015a0 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,82 @@ +# Development + +Some information on how to locally build and serve the website if you would like to make changes to the code. +There are two ways to do this: + +- docker + - closer to production environment + - but less convenient for development - you need to rebuild the image every time you make a change +- python/pnpm + - further from production environment setup + - but convenient for development - see changes immediately without having to rebuild or restart anything + +## Run locally with docker + +Requires docker and docker compose. + +1. clone the repo: + +```sh +git clone https://github.com/ssciwr/mondey.git +cd mondey +``` + +2. generate a local SSL cert/key pair: + +``` +openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 -nodes -subj '/CN=localhost' +``` + +3. build and run the website locally in docker containers on your computer: + +```sh +docker compose up --build -d +``` + +The website is then served at https://localhost/ +(note that the SSL keys are self-signed keys and your browser will still warn about the site being insecure.) + +Whenever you make a change to the code you need to re-run the above command to see the effect of your changes. + +### Database + +The databases will by default be stored in a `db` folder +in the folder where you run the docker compose command. + +### Make yourself an admin user + +``` +sudo sqlite3 docker_volume/predicTCR.db +sqlite> UPDATE user SET is_admin=true WHERE email='you@address.com'; +sqlite> .quit +``` + +## Run locally with Python and pnpm + +Requires Python and [pnpm](https://pnpm.io/installation#using-a-standalone-script) + +1. clone the repo: + +```sh +git clone https://github.com/ssciwr/mondey.git +cd mondey +``` + +2. install and run the backend development server: + +```sh +cd mondey_backend +pip install . +cd .. +mondey-backend +``` + +3. install and run the frontend development server: + +```sh +cd frontend +pnpm install +pnpm run dev +``` + +The website is then served at http://localhost:5173/. diff --git a/frontend/src/lib/admin.ts b/frontend/src/lib/admin.ts index dd32d488..a0c2da82 100644 --- a/frontend/src/lib/admin.ts +++ b/frontend/src/lib/admin.ts @@ -126,6 +126,105 @@ export function milestoneGroupImageUrl(id: number) { return `${import.meta.env.VITE_MONDEY_API_URL}/static/mg${id}.jpg`; } +export async function newMilestone(milestoneGroupId: number) { + console.log('newMilestone...'); + try { + const res = await fetch( + `${import.meta.env.VITE_MONDEY_API_URL}/admin/milestones/${milestoneGroupId}`, + { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json' + } + } + ); + if (res.status === 200) { + const newMilestone = await res.json(); + console.log(newMilestone); + await refreshMilestoneGroups(); + return newMilestone; + } else { + console.log('Failed to create new Milestone'); + } + } catch (e) { + console.error(e); + } + return null; +} + +export async function updateMilestone(milestone) { + console.log('updateMilestone...'); + console.log(milestone); + try { + const res = await fetch(`${import.meta.env.VITE_MONDEY_API_URL}/admin/milestones/`, { + method: 'PUT', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json' + }, + body: JSON.stringify(milestone) + }); + if (res.status === 200) { + const updatedMilestone = await res.json(); + console.log(updatedMilestone); + return updatedMilestone; + } else { + console.log('Failed to update Milestone'); + } + } catch (e) { + console.error(e); + } + return null; +} + +export async function uploadMilestoneImage(milestoneId: number, file) { + console.log('uploadMilestoneImage...'); + try { + const formData = new FormData(); + formData.append('file', file); + const res = await fetch( + `${import.meta.env.VITE_MONDEY_API_URL}/admin/milestone-images/${milestoneId}`, + { + method: 'POST', + credentials: 'include', + body: formData + } + ); + console.log(await res.json()); + } catch (e) { + console.error(e); + } +} + +export async function deleteMilestone(milestoneId: number | null) { + try { + const res = await fetch( + `${import.meta.env.VITE_MONDEY_API_URL}/admin/milestones/${milestoneId}`, + { + method: 'DELETE', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json' + } + } + ); + const json = await res.json(); + console.log(json); + if (res.status === 200) { + console.log(`Deleted Milestone with id ${milestoneId}.`); + await refreshMilestoneGroups(); + } else { + console.log(`Error deleting Milestone with id ${milestoneId}.`); + } + } catch (e) { + console.error(e); + } +} + export async function refreshUserQuestions() { console.log('refreshQuestions...'); try { diff --git a/frontend/src/lib/components/Admin/EditMilestoneModal.svelte b/frontend/src/lib/components/Admin/EditMilestoneModal.svelte new file mode 100644 index 00000000..09df804d --- /dev/null +++ b/frontend/src/lib/components/Admin/EditMilestoneModal.svelte @@ -0,0 +1,135 @@ + + + + {#if milestone} +
+ + {#each Object.entries(milestone.text) as [lang_id, text]} +
+ + {$languages[text.lang_id]} + + +
+ {/each} +
+
+ + {#each Object.entries(milestone.text) as [lang_id, text]} +
+ + {$languages[text.lang_id]} +