diff --git a/README.md b/README.md index dec35af..0a302b4 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,68 @@ -# Secure Web Development - Backend +# Web Application -This repo contains an Express app, it will be the backend used for the semester. +## Contenu du projet +### GeneralitĂ©s +Voici le lien du site produit par ce projet : [Webappfeli](https://webappfeli.netlify.app/) -## đŸ‘· Prerequisites +Ce projet consiste Ă  appliquer un front-end sur un [back-end](https://github.com/RochMoreau/secure-web-dev-backend) Ă©tabli prĂ©cĂ©demment. +Ce site a : +- Une page d'accueil + - Lorsque l'on clique sur la page log out, on est dĂ©connectĂ© et l'on est redirigĂ© vers la page d'accueil +- Une page de location + - Si l'on clique sur locations sans s'ĂȘtre connectĂ© auparavant, on est redirigĂ© vers la page de login + - Dans la page location; il est possible d'ouvrir une page de creation de location si l'utilisateur est un administrateur + - Malheureusement, je n'ai pas fait la liste des localisations de maniĂšre paginĂ©e comme demandĂ© +- Une page register + - La page register permet de se crĂ©er un compte si on n'en a pas -1. Fork this repository then clone it on your computer -2. Install Insomnia (or your API Testing tool of choice) and import the collection provided in [insomnia-collection.json](insomnia-collection.json) -3. If you don't have one, create a DB on Mongo Atlas -4. Create an `.env` file containing (replace with your data): - > MONGO_URI=mongodb+srv://USERNAME:PASSWORD@CLUSTER_URL/paris-films?retryWrites=true&w=majority - > - > JWT_SECRET=your-jwt-secret -5. Install NodeJS -6. Install dependencies : `npm install` -7. If you dont have data in your DB: - 1. Download the public dataset (OpenData) given by French gov and the city of Paris, named ["Lieux de tournage Ă  Paris"](https://opendata.paris.fr/explore/dataset/lieux-de-tournage-a-paris/information) - 2. Put the dataset at the root of this repository, named [lieux-de-tournage-a-paris.json](lieux-de-tournage-a-paris.json) - 3. Run the import script with `npm run import` -8. Run the backend `npm start` +Pour voir le front sans avoir dĂ©ployĂ©, il faut se dĂ©placer dans le dossier svelte (cd svelte au terminal), puis lancer la commande : "npm run dev" +Pour faire compiler la partie back sans avoir dĂ©ployĂ©, il faut juste lancer le projet (start index.js en fait), et aller sur un visualisateur comme insomnia ou postman. -## Quick information +### User Stories +Voici les objectifs initiaux Ă  atteindre : +- US1: As a random visitor, I want to be able to register an account or log-in, so I can +access all features => OK +- US2: As a random visitor, I want to be redirected to the login/register page when I click on +“location" tab, so that I know I must log-in to see its content => OK (vers login) +- US3: As a logged-in user, I want to see locations and be able to click on one location to +open a modal containing details, so that the website is useful => OK, mais il n'y a pas de paginations pour l'instant +- US4: As an “admin" user, I want to see a “Add Location” button in the location page, so +that I can create a new location => OK +- US5: As an “admin" user, I want to see a button to edit locations in the array of locations, +so that I can edit existing locations => OK +- US6: As an “admin" user, I want to see a button to delete locations in the array of + locations so that I can delete existing locations => OK -This backend connects to a MongoDB Database containing locations of film sets in Paris, France. +## Le code +### DĂ©marche +J'ai eu quelques difficultĂ©s Ă  saisir comment le back et le front allaient ĂȘtre connectĂ©s et communiquer ensemble. Je me suis beaucoup appuyĂ© sur un template de SvelteKit pour saisir le fonctionnement. -Consulting locations requires an account. Creating, updating and deleting locations requires elevated privileges. +### Composition du projet +- Les documents permettant le lancement de l'application, ou bien le remplissage de la base de donnĂ©es sont dans le dossier principal. +- Dans le dossier src, on peut trouver le back-end initial, construit avec javascript +- Dans le dossier svelte, on peut trouver le front-end, construit avec svelte kit + - Le dossier .netlify permet d'effectuer le dĂ©ploiement du front + - Le dossier public est un dossier demander lors du dĂ©ploiement, qui sert d'output + - Le dossier src contient les routes et les pages sveltes Ă  proprement parler -There are 2 access level: `user` and `admin`. Each user have a `role` property to store this data. +### Installations +J'ai installĂ© plusieurs Ă©lĂ©ments suplĂ©mentaires. Ils ne sont pas tous utiles, car parfois, je tatonnais juste, ou bien cherchais une solution en testant diffĂ©rentes options. -Users can authenticate themselves with a Json Web Token, obtained by logging-in with their `username` and `password`. +J'espĂšre que je n'en ai oubliĂ© aucun : +- npm create svelte@latest svelte (pour crĂ©er le projet svelte) +- npm install axios +- npm install vite +- npm install --save-dev nodemon +- npm install rollup +- npm install npx +- npm install cors +- npm install webpack +- npm install svelte +- npm install -g svelte-kit +- npm install -g fly-cli +- npm install flyctl +- npm install passport-jwt +- npm install yarn -Passwords are hashed, and the hashes are never shown in API responses. +# Le dĂ©ploiement +Pour le dĂ©ploiement, j'ai utilisĂ© Render pour le back et Netlify pour le front. Pour lier les deux, j'ai mis le lien gĂ©nĂ©rĂ© par le dĂ©ploiement du back-end Ă  la place de l'URL localhost. Ainsi, en dĂ©clenchant le front-end, je dĂ©clenche le back-end en rĂ©alitĂ©. Pour que les mises Ă  jours s'effectuent seules, je n'ai rien eu Ă  faire, cela fonctionnait automatiquement. \ No newline at end of file diff --git a/index.js b/index.js index 492950e..194b501 100644 --- a/index.js +++ b/index.js @@ -1,19 +1,20 @@ require("dotenv").config(); +require("./src/authentication/local.strategy"); +require("./src/authentication/jwt.strategy"); + +const bodyParser = require("body-parser"); const express = require("express"); +const cors = require("cors"); const locationsController = require("./src/locations/locations.controller"); -const usersController = require("./src/users/users.controller"); const mongoose = require("mongoose"); -const bodyParser = require("body-parser"); -require("./src/authentication/local.strategy"); -require("./src/authentication/jwt.strategy"); +mongoose.set('strictQuery', true); const passport = require("passport"); -const cors = require("cors"); +const port = process.env.PORT || 3000; +const usersController = require("./src/users/users.controller"); const app = express(); -const port = 3000; - app.use(bodyParser.json()); -app.use(cors("*")); +app.use(cors()); // Protect all /locations route with JWT Authentication app.use( @@ -32,7 +33,8 @@ async function main() { console.log( `API listening on port ${port}, visit http://localhost:${port}/` ); + }); } -main(); +main(); \ No newline at end of file diff --git a/package.json b/package.json index bc3b4ba..593a7e4 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,9 @@ "main": "index.js", "scripts": { "start": "node index.js", - "import": "node import-data.js" + "import": "node import-data.js", + "dev": "nodemon index.js", + "build": "svelte-kit build" }, "repository": { "type": "git", diff --git a/src/locations/locations.controller.js b/src/locations/locations.controller.js index bfab670..cb83a18 100644 --- a/src/locations/locations.controller.js +++ b/src/locations/locations.controller.js @@ -20,6 +20,7 @@ router.post( async function controllerGetAllLocations(req, res) { const limit = req.query.limit || 20; const offset = req.query.offset || 0; + //const locations = await locationsService.findAll(); const locations = await locationsService.findAll(limit, offset); return res.status(200).send(locations); } diff --git a/src/locations/locations.service.js b/src/locations/locations.service.js index 2c13400..195f09c 100644 --- a/src/locations/locations.service.js +++ b/src/locations/locations.service.js @@ -15,7 +15,7 @@ async function createOne(locationData) { * @param offset * @returns {Query>, Document & unknown extends {_id?: infer U} ? IfAny> : {_id: Types.ObjectId} & {}, {}, unknown> & {}} */ -function findAll(limit = 20, offset = 0) { +function findAll(limit = 30000, offset = 0) { return Location.find().limit(limit).skip(offset); } diff --git a/svelte/.gitignore b/svelte/.gitignore new file mode 100644 index 0000000..8f6c617 --- /dev/null +++ b/svelte/.gitignore @@ -0,0 +1,12 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example +.vercel +.output +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/svelte/.netlify/functions-internal/sveltekit-render.json b/svelte/.netlify/functions-internal/sveltekit-render.json new file mode 100644 index 0000000..98d3d2e --- /dev/null +++ b/svelte/.netlify/functions-internal/sveltekit-render.json @@ -0,0 +1 @@ +{"config":{"nodeModuleFormat":"esm"},"version":1} \ No newline at end of file diff --git a/svelte/.netlify/functions-internal/sveltekit-render.mjs b/svelte/.netlify/functions-internal/sveltekit-render.mjs new file mode 100644 index 0000000..c34068c --- /dev/null +++ b/svelte/.netlify/functions-internal/sveltekit-render.mjs @@ -0,0 +1,69 @@ +import { init } from '../serverless.js'; + +export const handler = init({ + appDir: "_app", + appPath: "_app", + assets: new Set(["logo.png","robots.txt"]), + mimeTypes: {".png":"image/png",".txt":"text/plain"}, + _: { + entry: {"file":"_app/immutable/start-2f9e8882.js","imports":["_app/immutable/start-2f9e8882.js","_app/immutable/chunks/index-103765ec.js","_app/immutable/chunks/singletons-40e1893b.js","_app/immutable/chunks/control-e7f5239e.js","_app/immutable/chunks/parse-b67c4dc9.js"],"stylesheets":[],"fonts":[]}, + nodes: [ + () => import('../server/nodes/0.js'), + () => import('../server/nodes/1.js'), + () => import('../server/nodes/2.js'), + () => import('../server/nodes/3.js'), + () => import('../server/nodes/4.js'), + () => import('../server/nodes/5.js'), + () => import('../server/nodes/6.js'), + () => import('../server/nodes/7.js') + ], + routes: [ + { + id: "/", + pattern: /^\/$/, + params: [], + page: { layouts: [0], errors: [1], leaf: 2 }, + endpoint: null + }, + { + id: "/add", + pattern: /^\/add\/?$/, + params: [], + page: { layouts: [0], errors: [1], leaf: 3 }, + endpoint: null + }, + { + id: "/locations", + pattern: /^\/locations\/?$/, + params: [], + page: { layouts: [0], errors: [1], leaf: 4 }, + endpoint: null + }, + { + id: "/login", + pattern: /^\/login\/?$/, + params: [], + page: { layouts: [0], errors: [1], leaf: 5 }, + endpoint: null + }, + { + id: "/logout", + pattern: /^\/logout\/?$/, + params: [], + page: { layouts: [0], errors: [1], leaf: 6 }, + endpoint: null + }, + { + id: "/register", + pattern: /^\/register\/?$/, + params: [], + page: { layouts: [0], errors: [1], leaf: 7 }, + endpoint: null + } + ], + matchers: async () => { + + return { }; + } + } +}); diff --git a/svelte/.netlify/server/_app/immutable/assets/_layout-9d380a46.css b/svelte/.netlify/server/_app/immutable/assets/_layout-9d380a46.css new file mode 100644 index 0000000..568963f --- /dev/null +++ b/svelte/.netlify/server/_app/immutable/assets/_layout-9d380a46.css @@ -0,0 +1,147 @@ +header.svelte-i4oixz.svelte-i4oixz{display:flex;justify-content:space-between}.corner.svelte-i4oixz.svelte-i4oixz{width:3em;height:3em}nav.svelte-i4oixz.svelte-i4oixz{display:flex;justify-content:center;--background:rgba(255, 255, 255, 0.7)}ul.svelte-i4oixz.svelte-i4oixz{position:relative;padding:0;margin:0;height:3em;display:flex;justify-content:center;align-items:center;list-style:none;background:var(--background);background-size:contain;border-radius:10px}li.svelte-i4oixz.svelte-i4oixz{position:relative;height:100%}li[aria-current='page'].svelte-i4oixz.svelte-i4oixz::before{--size:6px;content:'';width:0;height:0;position:absolute;top:0;left:calc(50% - var(--size));border:var(--size) solid transparent;border-top:var(--size) solid var(--color-theme-1)}nav.svelte-i4oixz a.svelte-i4oixz{display:flex;height:100%;align-items:center;padding:0 4rem;color:var(--color-text);font-weight:700;font-size:1rem;text-transform:uppercase;letter-spacing:0.1em;text-decoration:none;transition:color 0.2s linear}a.svelte-i4oixz.svelte-i4oixz:hover{color:var(--color-theme-1)}/* fira-mono-cyrillic-ext-400-normal*/ +@font-face { + font-family: 'Fira Mono'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: url('http://github.com/_app/immutable/assets/fira-mono-cyrillic-ext-400-normal-3df7909e.woff2') format('woff2'), url('http://github.com/_app/immutable/assets/fira-mono-all-400-normal-1e3b098c.woff') format('woff'); + unicode-range: U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F; +} +/* fira-mono-cyrillic-400-normal*/ +@font-face { + font-family: 'Fira Mono'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: url('http://github.com/_app/immutable/assets/fira-mono-cyrillic-400-normal-c7d433fd.woff2') format('woff2'), url('http://github.com/_app/immutable/assets/fira-mono-all-400-normal-1e3b098c.woff') format('woff'); + unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116; +} +/* fira-mono-greek-ext-400-normal*/ +@font-face { + font-family: 'Fira Mono'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: url('http://github.com/_app/immutable/assets/fira-mono-greek-ext-400-normal-9e2fe623.woff2') format('woff2'), url('http://github.com/_app/immutable/assets/fira-mono-all-400-normal-1e3b098c.woff') format('woff'); + unicode-range: U+1F00-1FFF; +} +/* fira-mono-greek-400-normal*/ +@font-face { + font-family: 'Fira Mono'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: url('http://github.com/_app/immutable/assets/fira-mono-greek-400-normal-a8be01ce.woff2') format('woff2'), url('http://github.com/_app/immutable/assets/fira-mono-all-400-normal-1e3b098c.woff') format('woff'); + unicode-range: U+0370-03FF; +} +/* fira-mono-latin-ext-400-normal*/ +@font-face { + font-family: 'Fira Mono'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: url('http://github.com/_app/immutable/assets/fira-mono-latin-ext-400-normal-6bfabd30.woff2') format('woff2'), url('http://github.com/_app/immutable/assets/fira-mono-all-400-normal-1e3b098c.woff') format('woff'); + unicode-range: U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF; +} +/* fira-mono-latin-400-normal*/ +@font-face { + font-family: 'Fira Mono'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: url('http://github.com/_app/immutable/assets/fira-mono-latin-400-normal-e43b3538.woff2') format('woff2'), url('http://github.com/_app/immutable/assets/fira-mono-all-400-normal-1e3b098c.woff') format('woff'); + unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; +} +:root { + --font-body: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, + Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + --font-mono: 'Fira Mono', monospace; + --color-bg-0: rgb(202, 216, 228); + --color-bg-1: hsl(209, 36%, 86%); + --color-bg-2: hsl(224, 44%, 95%); + --color-theme-1: #ff3e00; + --color-theme-2: #4075a6; + --color-text: rgb(182, 234, 218); + --column-width: 42rem; + --column-margin-top: 4rem; + font-family: var(--font-body); + color: var(--color-text); +} +body { + min-height: 100vh; + margin: 0; + background-attachment: fixed; + background-color: var(--color-bg-1); + background-size: 100vw 100vh; + background-image: radial-gradient( + 50% 50% at 50% 50%, + rgba(255, 255, 255, 0.75) 0%, + rgba(255, 255, 255, 0) 100% + ), + linear-gradient(180deg, var(--color-bg-0) 0%, var(--color-bg-1) 15%, var(--color-bg-2) 50%); +} +h1, +h2, +p { + font-weight: 400; +} +p { + line-height: 1.5; +} +a { + color: var(--color-theme-1); + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +h1 { + font-size: 2rem; + text-align: center; +} +h2 { + font-size: 1rem; +} +pre { + font-size: 16px; + font-family: var(--font-mono); + background-color: rgba(255, 255, 255, 0.45); + border-radius: 3px; + box-shadow: 2px 2px 6px rgb(255 255 255 / 25%); + padding: 0.5em; + overflow-x: auto; + color: var(--color-text); +} +.text-column { + display: flex; + max-width: 48rem; + flex: 0.6; + flex-direction: column; + justify-content: center; + margin: 0 auto; +} +input, +button { + font-size: inherit; + font-family: inherit; +} +button:focus:not(:focus-visible) { + outline: none; +} +@media (min-width: 720px) { + h1 { + font-size: 2.4rem; + } +} +.visually-hidden { + border: 0; + clip: rect(0 0 0 0); + height: auto; + margin: 0; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + white-space: nowrap; +} +.app.svelte-8o1gnw.svelte-8o1gnw{display:flex;flex-direction:column;min-height:100vh}main.svelte-8o1gnw.svelte-8o1gnw{flex:1;display:flex;flex-direction:column;padding:1rem;width:100%;max-width:64rem;margin:0 auto;box-sizing:border-box}footer.svelte-8o1gnw.svelte-8o1gnw{display:flex;flex-direction:column;justify-content:center;align-items:center;padding:12px}footer.svelte-8o1gnw a.svelte-8o1gnw{font-weight:bold}@media(min-width: 480px){footer.svelte-8o1gnw.svelte-8o1gnw{padding:12px 0}} \ No newline at end of file diff --git a/svelte/.netlify/server/_app/immutable/assets/_page-5fbedd8c.css b/svelte/.netlify/server/_app/immutable/assets/_page-5fbedd8c.css new file mode 100644 index 0000000..35b8239 --- /dev/null +++ b/svelte/.netlify/server/_app/immutable/assets/_page-5fbedd8c.css @@ -0,0 +1 @@ +section.svelte-39krom{display:flex;flex-direction:column;justify-content:center;align-items:center;flex:0.6}h1.svelte-39krom{width:100%} \ No newline at end of file diff --git a/svelte/.netlify/server/_app/immutable/assets/_page-8a32a4f1.css b/svelte/.netlify/server/_app/immutable/assets/_page-8a32a4f1.css new file mode 100644 index 0000000..387b626 --- /dev/null +++ b/svelte/.netlify/server/_app/immutable/assets/_page-8a32a4f1.css @@ -0,0 +1 @@ +.svelte-1pxeemu{background-color:#F6F6F6}form.svelte-1pxeemu{width:50%;margin:0 auto;padding:20px;background-color:#fff;border-radius:10px}label.svelte-1pxeemu{display:block;margin-bottom:10px}input[type="text"].svelte-1pxeemu{width:100%;padding:10px;margin-bottom:20px;border:4px solid #ccc;border-radius:5px}button[type="submit"].svelte-1pxeemu{width:100%;padding:10px;background-color:#3f51b5;color:#fff;border:none;border-radius:5px;cursor:pointer}button[type="submit"].svelte-1pxeemu:hover{background-color:grey}h1.svelte-1pxeemu{text-align:center;font-size:40px;margin-top:10px;color:black}.return-main.svelte-1pxeemu{position:absolute;top:40px;right:10px;margin:0;padding:10px 20px;background-color:lightgray;border:none;border-radius:5px;font-size:16px;cursor:pointer}.return-main.svelte-1pxeemu:hover{color:#000000;background-color:grey}.location-added.svelte-1pxeemu{font-size:20px;color:green;text-align:center} \ No newline at end of file diff --git a/svelte/.netlify/server/_app/immutable/assets/fira-mono-all-400-normal-1e3b098c.woff b/svelte/.netlify/server/_app/immutable/assets/fira-mono-all-400-normal-1e3b098c.woff new file mode 100644 index 0000000..7f61fe9 Binary files /dev/null and b/svelte/.netlify/server/_app/immutable/assets/fira-mono-all-400-normal-1e3b098c.woff differ diff --git a/svelte/.netlify/server/_app/immutable/assets/fira-mono-cyrillic-400-normal-c7d433fd.woff2 b/svelte/.netlify/server/_app/immutable/assets/fira-mono-cyrillic-400-normal-c7d433fd.woff2 new file mode 100644 index 0000000..48cf9cf Binary files /dev/null and b/svelte/.netlify/server/_app/immutable/assets/fira-mono-cyrillic-400-normal-c7d433fd.woff2 differ diff --git a/svelte/.netlify/server/_app/immutable/assets/fira-mono-cyrillic-ext-400-normal-3df7909e.woff2 b/svelte/.netlify/server/_app/immutable/assets/fira-mono-cyrillic-ext-400-normal-3df7909e.woff2 new file mode 100644 index 0000000..d633202 Binary files /dev/null and b/svelte/.netlify/server/_app/immutable/assets/fira-mono-cyrillic-ext-400-normal-3df7909e.woff2 differ diff --git a/svelte/.netlify/server/_app/immutable/assets/fira-mono-greek-400-normal-a8be01ce.woff2 b/svelte/.netlify/server/_app/immutable/assets/fira-mono-greek-400-normal-a8be01ce.woff2 new file mode 100644 index 0000000..01061b4 Binary files /dev/null and b/svelte/.netlify/server/_app/immutable/assets/fira-mono-greek-400-normal-a8be01ce.woff2 differ diff --git a/svelte/.netlify/server/_app/immutable/assets/fira-mono-greek-ext-400-normal-9e2fe623.woff2 b/svelte/.netlify/server/_app/immutable/assets/fira-mono-greek-ext-400-normal-9e2fe623.woff2 new file mode 100644 index 0000000..4dc2f4b Binary files /dev/null and b/svelte/.netlify/server/_app/immutable/assets/fira-mono-greek-ext-400-normal-9e2fe623.woff2 differ diff --git a/svelte/.netlify/server/_app/immutable/assets/fira-mono-latin-400-normal-e43b3538.woff2 b/svelte/.netlify/server/_app/immutable/assets/fira-mono-latin-400-normal-e43b3538.woff2 new file mode 100644 index 0000000..edc71a8 Binary files /dev/null and b/svelte/.netlify/server/_app/immutable/assets/fira-mono-latin-400-normal-e43b3538.woff2 differ diff --git a/svelte/.netlify/server/_app/immutable/assets/fira-mono-latin-ext-400-normal-6bfabd30.woff2 b/svelte/.netlify/server/_app/immutable/assets/fira-mono-latin-ext-400-normal-6bfabd30.woff2 new file mode 100644 index 0000000..43e41c8 Binary files /dev/null and b/svelte/.netlify/server/_app/immutable/assets/fira-mono-latin-ext-400-normal-6bfabd30.woff2 differ diff --git a/svelte/.netlify/server/chunks/api.js b/svelte/.netlify/server/chunks/api.js new file mode 100644 index 0000000..2e79239 --- /dev/null +++ b/svelte/.netlify/server/chunks/api.js @@ -0,0 +1,45 @@ +import { r as redirect, e as error } from "./index.js"; +//const base = "https://webapploc.onrender.com/"; +const base = "http://localhost:3000/"; +async function send({ method, path, data, token }) { + const opts = { method, headers: {} }; + if (data) { + opts.headers["Content-Type"] = "application/json"; + opts.body = JSON.stringify(data); + } + if (token) { + opts.headers["Authorization"] = `Bearer ${token}`; + } + const res = await fetch(`${base}/${path}`, opts); + if (res.status == 200 || res.status === 201) { + const text = await res.text(); + return text ? JSON.parse(text) : {}; + } + if (res.status === 401) { + if (method === "POST") { + throw redirect(302, `/login?error=true`); + } + throw redirect(302, `/login`); + } + if (res.status === 403) { + throw redirect(302, `/login`); + } + if (res.status === 400) { + throw redirect(302, `/login`); + } + throw error(res.status); +} +function get(path, token) { + return send({ method: "GET", path, token }); +} +function post(path, data, token) { + return send({ method: "POST", path, data, token }); +} +function patch(path, data, token) { + return send({ method: "PATCH", path, data, token }); +} +export { + patch as a, + get as g, + post as p +}; diff --git a/svelte/.netlify/server/chunks/hooks.server.js b/svelte/.netlify/server/chunks/hooks.server.js new file mode 100644 index 0000000..8c5d1fd --- /dev/null +++ b/svelte/.netlify/server/chunks/hooks.server.js @@ -0,0 +1,8 @@ +function handle({ event, resolve }) { + const jwt = event.cookies.get("jwt"); + event.locals.jwt = jwt ? jwt : null; + return resolve(event); +} +export { + handle +}; diff --git a/svelte/.netlify/server/chunks/index.js b/svelte/.netlify/server/chunks/index.js new file mode 100644 index 0000000..4f9d027 --- /dev/null +++ b/svelte/.netlify/server/chunks/index.js @@ -0,0 +1,89 @@ +let HttpError = class HttpError2 { + /** + * @param {number} status + * @param {{message: string} extends App.Error ? (App.Error | string | undefined) : App.Error} body + */ + constructor(status, body) { + this.status = status; + if (typeof body === "string") { + this.body = { message: body }; + } else if (body) { + this.body = body; + } else { + this.body = { message: `Error: ${status}` }; + } + } + toString() { + return JSON.stringify(this.body); + } +}; +let Redirect = class Redirect2 { + /** + * @param {300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308} status + * @param {string} location + */ + constructor(status, location) { + this.status = status; + this.location = location; + } +}; +let ActionFailure = class ActionFailure2 { + /** + * @param {number} status + * @param {T} [data] + */ + constructor(status, data) { + this.status = status; + this.data = data; + } +}; +function error(status, message) { + if (isNaN(status) || status < 400 || status > 599) { + throw new Error(`HTTP error status codes must be between 400 and 599 — ${status} is invalid`); + } + return new HttpError(status, message); +} +function redirect(status, location) { + if (isNaN(status) || status < 300 || status > 308) { + throw new Error("Invalid status code"); + } + return new Redirect(status, location); +} +function json(data, init) { + const body = JSON.stringify(data); + const headers = new Headers(init?.headers); + if (!headers.has("content-length")) { + headers.set("content-length", encoder.encode(body).byteLength.toString()); + } + if (!headers.has("content-type")) { + headers.set("content-type", "application/json"); + } + return new Response(body, { + ...init, + headers + }); +} +const encoder = new TextEncoder(); +function text(body, init) { + const headers = new Headers(init?.headers); + if (!headers.has("content-length")) { + headers.set("content-length", encoder.encode(body).byteLength.toString()); + } + return new Response(body, { + ...init, + headers + }); +} +function fail(status, data) { + return new ActionFailure(status, data); +} +export { + ActionFailure as A, + HttpError as H, + Redirect as R, + error as e, + fail as f, + json as j, + redirect as r, + text as t +}; diff --git a/svelte/.netlify/server/chunks/index2.js b/svelte/.netlify/server/chunks/index2.js new file mode 100644 index 0000000..6b0df4b --- /dev/null +++ b/svelte/.netlify/server/chunks/index2.js @@ -0,0 +1,128 @@ +function noop() { +} +function run(fn) { + return fn(); +} +function blank_object() { + return /* @__PURE__ */ Object.create(null); +} +function run_all(fns) { + fns.forEach(run); +} +function safe_not_equal(a, b) { + return a != a ? b == b : a !== b || (a && typeof a === "object" || typeof a === "function"); +} +function subscribe(store, ...callbacks) { + if (store == null) { + return noop; + } + const unsub = store.subscribe(...callbacks); + return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub; +} +let current_component; +function set_current_component(component) { + current_component = component; +} +function get_current_component() { + if (!current_component) + throw new Error("Function called outside component initialization"); + return current_component; +} +function setContext(key, context) { + get_current_component().$$.context.set(key, context); + return context; +} +function getContext(key) { + return get_current_component().$$.context.get(key); +} +Promise.resolve(); +const ATTR_REGEX = /[&"]/g; +const CONTENT_REGEX = /[&<]/g; +function escape(value, is_attr = false) { + const str = String(value); + const pattern = is_attr ? ATTR_REGEX : CONTENT_REGEX; + pattern.lastIndex = 0; + let escaped = ""; + let last = 0; + while (pattern.test(str)) { + const i = pattern.lastIndex - 1; + const ch = str[i]; + escaped += str.substring(last, i) + (ch === "&" ? "&" : ch === '"' ? """ : "<"); + last = i + 1; + } + return escaped + str.substring(last); +} +function each(items, fn) { + let str = ""; + for (let i = 0; i < items.length; i += 1) { + str += fn(items[i], i); + } + return str; +} +const missing_component = { + $$render: () => "" +}; +function validate_component(component, name) { + if (!component || !component.$$render) { + if (name === "svelte:component") + name += " this={...}"; + throw new Error(`<${name}> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules. Otherwise you may need to fix a <${name}>.`); + } + return component; +} +let on_destroy; +function create_ssr_component(fn) { + function $$render(result, props, bindings, slots, context) { + const parent_component = current_component; + const $$ = { + on_destroy, + context: new Map(context || (parent_component ? parent_component.$$.context : [])), + // these will be immediately discarded + on_mount: [], + before_update: [], + after_update: [], + callbacks: blank_object() + }; + set_current_component({ $$ }); + const html = fn(result, props, bindings, slots); + set_current_component(parent_component); + return html; + } + return { + render: (props = {}, { $$slots = {}, context = /* @__PURE__ */ new Map() } = {}) => { + on_destroy = []; + const result = { title: "", head: "", css: /* @__PURE__ */ new Set() }; + const html = $$render(result, props, {}, $$slots, context); + run_all(on_destroy); + return { + html, + css: { + code: Array.from(result.css).map((css) => css.code).join("\n"), + map: null + // TODO + }, + head: result.title + result.head + }; + }, + $$render + }; +} +function add_attribute(name, value, boolean) { + if (value == null || boolean && !value) + return ""; + const assignment = boolean && value === true ? "" : `="${escape(value, true)}"`; + return ` ${name}${assignment}`; +} +export { + setContext as a, + subscribe as b, + create_ssr_component as c, + add_attribute as d, + escape as e, + each as f, + getContext as g, + missing_component as m, + noop as n, + safe_not_equal as s, + validate_component as v +}; diff --git a/svelte/.netlify/server/chunks/internal.js b/svelte/.netlify/server/chunks/internal.js new file mode 100644 index 0000000..c3d593e --- /dev/null +++ b/svelte/.netlify/server/chunks/internal.js @@ -0,0 +1,171 @@ +import { c as create_ssr_component, a as setContext, v as validate_component, m as missing_component } from "./index2.js"; +const base = ""; +let assets = base; +function set_assets(path) { + assets = path; +} +let version = ""; +let public_env = {}; +function set_building(value) { +} +function set_private_env(environment) { +} +function set_public_env(environment) { + public_env = environment; +} +function set_version(value) { + version = value; +} +function afterUpdate() { +} +const Root = create_ssr_component(($$result, $$props, $$bindings, slots) => { + let { stores } = $$props; + let { page } = $$props; + let { constructors } = $$props; + let { components = [] } = $$props; + let { form } = $$props; + let { data_0 = null } = $$props; + let { data_1 = null } = $$props; + { + setContext("__svelte__", stores); + } + afterUpdate(stores.page.notify); + if ($$props.stores === void 0 && $$bindings.stores && stores !== void 0) + $$bindings.stores(stores); + if ($$props.page === void 0 && $$bindings.page && page !== void 0) + $$bindings.page(page); + if ($$props.constructors === void 0 && $$bindings.constructors && constructors !== void 0) + $$bindings.constructors(constructors); + if ($$props.components === void 0 && $$bindings.components && components !== void 0) + $$bindings.components(components); + if ($$props.form === void 0 && $$bindings.form && form !== void 0) + $$bindings.form(form); + if ($$props.data_0 === void 0 && $$bindings.data_0 && data_0 !== void 0) + $$bindings.data_0(data_0); + if ($$props.data_1 === void 0 && $$bindings.data_1 && data_1 !== void 0) + $$bindings.data_1(data_1); + let $$settled; + let $$rendered; + do { + $$settled = true; + { + stores.page.set(page); + } + $$rendered = ` + + +${constructors[1] ? `${validate_component(constructors[0] || missing_component, "svelte:component").$$render( + $$result, + { data: data_0, this: components[0] }, + { + this: ($$value) => { + components[0] = $$value; + $$settled = false; + } + }, + { + default: () => { + return `${validate_component(constructors[1] || missing_component, "svelte:component").$$render( + $$result, + { data: data_1, form, this: components[1] }, + { + this: ($$value) => { + components[1] = $$value; + $$settled = false; + } + }, + {} + )}`; + } + } + )}` : `${validate_component(constructors[0] || missing_component, "svelte:component").$$render( + $$result, + { data: data_0, form, this: components[0] }, + { + this: ($$value) => { + components[0] = $$value; + $$settled = false; + } + }, + {} + )}`} + +${``}`; + } while (!$$settled); + return $$rendered; +}); +set_version("1676825639610"); +const options = { + csp: { "mode": "auto", "directives": { "upgrade-insecure-requests": false, "block-all-mixed-content": false }, "reportOnly": { "upgrade-insecure-requests": false, "block-all-mixed-content": false } }, + csrf_check_origin: true, + embedded: false, + env_public_prefix: "PUBLIC_", + hooks: null, + // added lazily, via `get_hooks` + root: Root, + service_worker: false, + templates: { + app: ({ head, body, assets: assets2, nonce, env }) => '\n\n \n \n \n \n ' + head + '\n \n \n
' + body + "
\n \n\n", + error: ({ status, message }) => '\n\n \n \n ' + message + ` + + + + +
+ ` + status + '\n
\n

' + message + "

\n
\n
\n \n\n" + } +}; +function get_hooks() { + return import("./hooks.server.js"); +} +export { + assets as a, + base as b, + set_assets as c, + set_building as d, + set_private_env as e, + get_hooks as g, + options as o, + public_env as p, + set_public_env as s, + version as v +}; diff --git a/svelte/.netlify/server/chunks/stores.js b/svelte/.netlify/server/chunks/stores.js new file mode 100644 index 0000000..758c9c4 --- /dev/null +++ b/svelte/.netlify/server/chunks/stores.js @@ -0,0 +1,23 @@ +import { g as getContext } from "./index2.js"; +const getStores = () => { + const stores = getContext("__svelte__"); + return { + page: { + subscribe: stores.page.subscribe + }, + navigating: { + subscribe: stores.navigating.subscribe + }, + updated: stores.updated + }; +}; +const page = { + /** @param {(value: any) => void} fn */ + subscribe(fn) { + const store = getStores().page; + return store.subscribe(fn); + } +}; +export { + page as p +}; diff --git a/svelte/.netlify/server/entries/fallbacks/error.svelte.js b/svelte/.netlify/server/entries/fallbacks/error.svelte.js new file mode 100644 index 0000000..6054910 --- /dev/null +++ b/svelte/.netlify/server/entries/fallbacks/error.svelte.js @@ -0,0 +1,12 @@ +import { c as create_ssr_component, b as subscribe, e as escape } from "../../chunks/index2.js"; +import { p as page } from "../../chunks/stores.js"; +const Error = create_ssr_component(($$result, $$props, $$bindings, slots) => { + let $page, $$unsubscribe_page; + $$unsubscribe_page = subscribe(page, (value) => $page = value); + $$unsubscribe_page(); + return `

${escape($page.status)}

+

${escape($page.error?.message)}

`; +}); +export { + Error as default +}; diff --git a/svelte/.netlify/server/entries/pages/_layout.server.js b/svelte/.netlify/server/entries/pages/_layout.server.js new file mode 100644 index 0000000..c21d42d --- /dev/null +++ b/svelte/.netlify/server/entries/pages/_layout.server.js @@ -0,0 +1,8 @@ +function load({ locals }) { + return { + jwt: locals.jwt + }; +} +export { + load +}; diff --git a/svelte/.netlify/server/entries/pages/_layout.svelte.js b/svelte/.netlify/server/entries/pages/_layout.svelte.js new file mode 100644 index 0000000..0e9b459 --- /dev/null +++ b/svelte/.netlify/server/entries/pages/_layout.svelte.js @@ -0,0 +1,40 @@ +import { c as create_ssr_component, b as subscribe, d as add_attribute, v as validate_component } from "../../chunks/index2.js"; +import { p as page } from "../../chunks/stores.js"; +const Header_svelte_svelte_type_style_lang = ""; +const css$1 = { + code: "header.svelte-i4oixz.svelte-i4oixz{display:flex;justify-content:space-between}.corner.svelte-i4oixz.svelte-i4oixz{width:3em;height:3em}nav.svelte-i4oixz.svelte-i4oixz{display:flex;justify-content:center;--background:rgba(255, 255, 255, 0.7)}ul.svelte-i4oixz.svelte-i4oixz{position:relative;padding:0;margin:0;height:3em;display:flex;justify-content:center;align-items:center;list-style:none;background:var(--background);background-size:contain;border-radius:10px}li.svelte-i4oixz.svelte-i4oixz{position:relative;height:100%}li[aria-current='page'].svelte-i4oixz.svelte-i4oixz::before{--size:6px;content:'';width:0;height:0;position:absolute;top:0;left:calc(50% - var(--size));border:var(--size) solid transparent;border-top:var(--size) solid var(--color-theme-1)}nav.svelte-i4oixz a.svelte-i4oixz{display:flex;height:100%;align-items:center;padding:0 4rem;color:var(--color-text);font-weight:700;font-size:1rem;text-transform:uppercase;letter-spacing:0.1em;text-decoration:none;transition:color 0.2s linear}a.svelte-i4oixz.svelte-i4oixz:hover{color:var(--color-theme-1)}", + map: null +}; +const Header = create_ssr_component(($$result, $$props, $$bindings, slots) => { + let $page, $$unsubscribe_page; + $$unsubscribe_page = subscribe(page, (value) => $page = value); + $$result.css.add(css$1); + $$unsubscribe_page(); + return `
+ + + +
+
`; +}); +const styles = ""; +const _layout_svelte_svelte_type_style_lang = ""; +const css = { + code: ".app.svelte-8o1gnw.svelte-8o1gnw{display:flex;flex-direction:column;min-height:100vh}main.svelte-8o1gnw.svelte-8o1gnw{flex:1;display:flex;flex-direction:column;padding:1rem;width:100%;max-width:64rem;margin:0 auto;box-sizing:border-box}footer.svelte-8o1gnw.svelte-8o1gnw{display:flex;flex-direction:column;justify-content:center;align-items:center;padding:12px}footer.svelte-8o1gnw a.svelte-8o1gnw{font-weight:bold}@media(min-width: 480px){footer.svelte-8o1gnw.svelte-8o1gnw{padding:12px 0}}", + map: null +}; +const Layout = create_ssr_component(($$result, $$props, $$bindings, slots) => { + $$result.css.add(css); + return `
${validate_component(Header, "Header").$$render($$result, {}, {}, {})} + +
${slots.default ? slots.default({}) : ``}
+ + +
`; +}); +export { + Layout as default +}; diff --git a/svelte/.netlify/server/entries/pages/_page.server.js b/svelte/.netlify/server/entries/pages/_page.server.js new file mode 100644 index 0000000..97c4f1b --- /dev/null +++ b/svelte/.netlify/server/entries/pages/_page.server.js @@ -0,0 +1,7 @@ +import "../../chunks/index.js"; +async function load({ parent }) { + await parent(); +} +export { + load +}; diff --git a/svelte/.netlify/server/entries/pages/_page.svelte.js b/svelte/.netlify/server/entries/pages/_page.svelte.js new file mode 100644 index 0000000..0f774fc --- /dev/null +++ b/svelte/.netlify/server/entries/pages/_page.svelte.js @@ -0,0 +1,17 @@ +import { c as create_ssr_component } from "../../chunks/index2.js"; +const _page_svelte_svelte_type_style_lang = ""; +const css = { + code: "section.svelte-39krom{display:flex;flex-direction:column;justify-content:center;align-items:center;flex:0.6}h1.svelte-39krom{width:100%}", + map: null +}; +const Page = create_ssr_component(($$result, $$props, $$bindings, slots) => { + $$result.css.add(css); + return `${$$result.head += `${$$result.title = `Home`, ""}`, ""} + +

Welcome on this experimental website +

+
`; +}); +export { + Page as default +}; diff --git a/svelte/.netlify/server/entries/pages/add/_page.server.js b/svelte/.netlify/server/entries/pages/add/_page.server.js new file mode 100644 index 0000000..27481b6 --- /dev/null +++ b/svelte/.netlify/server/entries/pages/add/_page.server.js @@ -0,0 +1,40 @@ +import { r as redirect, f as fail } from "../../../chunks/index.js"; +import { p as post } from "../../../chunks/api.js"; +async function load({ locals, url }) { + let jwt = locals.jwt; + if (jwt == null) { + throw redirect(307, "/login"); + } + let role = JSON.parse(atob(jwt.split(".")[1])).role; + if (role != "admin") { + throw redirect(307, "/locations"); + } + let para = url.searchParams.get("success"); + return { para }; +} +const actions = { + default: async ({ request, locals }) => { + const data = await request.formData(); + const user = { + filmType: data.get("filmType"), + filmProducerName: data.get("filmProducerName"), + endDate: data.get("endDate"), + filmName: data.get("filmName"), + district: data.get("district"), + sourceLocationId: data.get("sourceLocationId"), + filmDirectorName: data.get("filmDirectorName"), + address: data.get("address"), + startDate: data.get("startDate"), + year: data.get("year") + }; + const body = await post("locations/", user, locals.jwt); + if (body.errors) { + return fail(401, body); + } + throw redirect(307, "/add?success=true"); + } +}; +export { + actions, + load +}; diff --git a/svelte/.netlify/server/entries/pages/add/_page.svelte.js b/svelte/.netlify/server/entries/pages/add/_page.svelte.js new file mode 100644 index 0000000..4418d11 --- /dev/null +++ b/svelte/.netlify/server/entries/pages/add/_page.svelte.js @@ -0,0 +1,42 @@ +import { c as create_ssr_component } from "../../../chunks/index2.js"; +import "devalue"; +const _page_svelte_svelte_type_style_lang = ""; +const css = { + code: '.svelte-1pxeemu{background-color:#F6F6F6}form.svelte-1pxeemu{width:50%;margin:0 auto;padding:20px;background-color:#fff;border-radius:10px}label.svelte-1pxeemu{display:block;margin-bottom:10px}input[type="text"].svelte-1pxeemu{width:100%;padding:10px;margin-bottom:20px;border:4px solid #ccc;border-radius:5px}button[type="submit"].svelte-1pxeemu{width:100%;padding:10px;background-color:#3f51b5;color:#fff;border:none;border-radius:5px;cursor:pointer}button[type="submit"].svelte-1pxeemu:hover{background-color:grey}h1.svelte-1pxeemu{text-align:center;font-size:40px;margin-top:10px;color:black}.return-main.svelte-1pxeemu{position:absolute;top:40px;right:10px;margin:0;padding:10px 20px;background-color:lightgray;border:none;border-radius:5px;font-size:16px;cursor:pointer}.return-main.svelte-1pxeemu:hover{color:#000000;background-color:grey}.location-added.svelte-1pxeemu{font-size:20px;color:green;text-align:center}', + map: null +}; +const Page = create_ssr_component(($$result, $$props, $$bindings, slots) => { + let { data } = $$props; + if ($$props.data === void 0 && $$bindings.data && data !== void 0) + $$bindings.data(data); + $$result.css.add(css); + return `${$$result.head += `${$$result.title = `Add a Location`, ""}`, ""} + +

Add a Location

+ ${data.para ? `

Locations Added

` : ``} +
+ + + + + + + + +
+
+ +
+ `; +}); +export { + Page as default +}; diff --git a/svelte/.netlify/server/entries/pages/locations/_page.server.js b/svelte/.netlify/server/entries/pages/locations/_page.server.js new file mode 100644 index 0000000..15fb33a --- /dev/null +++ b/svelte/.netlify/server/entries/pages/locations/_page.server.js @@ -0,0 +1,34 @@ +import { r as redirect } from "../../../chunks/index.js"; +import { g as get, a as patch } from "../../../chunks/api.js"; +async function load({ locals, request, response }) { + let jwt = locals.jwt; + let body = await get("locations", jwt); + return { body, jwt }; +} +const actions = { + // @ts-ignore + default: async ({ request, locals }) => { + const data = await request.formData(); + const user = { + _id: data.get("_id"), + filmType: data.get("filmType"), + filmProducerName: data.get("filmProducerName"), + endDate: data.get("endDate"), + filmName: data.get("filmName"), + district: data.get("district"), + coordinates: data.get("coordinates"), + sourceLocationId: data.get("sourceLocationId"), + filmDirectorName: data.get("filmDirectorName"), + address: data.get("address"), + startDate: data.get("startDate"), + year: data.get("year"), + __v: data.get("__v") + }; + await patch(`locations/${user._id}`, user, locals.jwt); + throw redirect(307, "/locations"); + } +}; +export { + actions, + load +}; diff --git a/svelte/.netlify/server/entries/pages/locations/_page.svelte.js b/svelte/.netlify/server/entries/pages/locations/_page.svelte.js new file mode 100644 index 0000000..f2423b9 --- /dev/null +++ b/svelte/.netlify/server/entries/pages/locations/_page.svelte.js @@ -0,0 +1,63 @@ +import { c as create_ssr_component, f as each, e as escape, d as add_attribute } from "../../../chunks/index2.js"; +import "devalue"; +import "../../../chunks/index.js"; +const Page = create_ssr_component(($$result, $$props, $$bindings, slots) => { + let { data } = $$props; + let locations = data.body; + let role = JSON.parse(atob(data.jwt.split(".")[1])).role; + if ($$props.data === void 0 && $$bindings.data && data !== void 0) + $$bindings.data(data); + return `${$$result.head += `${$$result.title = `Locations`, ""}`, ""} + +

Locations in Paris

+

Click on a movie to get its details.

+ + ${role == "admin" ? `Add a Location` : ``} + + ${locations.length === 0 ? `

Loading...

` : `
    ${each(locations, (location) => { + return `
  • +

    ${escape(location.filmName)} - ID : ${escape(location._id)}

    + ${location.showDetails ? `${role == "admin" ? `
    + + + + + + + + + + + +
    + ` : ``} + ${role != "admin" ? `
  • ID: ${escape(location._id)}
  • +
  • Film Type: ${escape(location.filmType)}
  • +
  • Film Producer Name: ${escape(location.filmProducerName)}
  • +
  • End Date: ${escape(location.endDate)}
  • +
  • Film Name: ${escape(location.filmName)}
  • +
  • District: ${escape(location.district)}
  • +
  • Geolocation: ${escape(location.geolocation)}
  • +
  • Coordinates: ${escape(location.coordinates)}
  • +
  • Type: ${escape(location.type)}
  • +
  • Source Location ID: ${escape(location.sourceLocationId)}
  • +
  • Film Director Name: ${escape(location.filmDirectorName)}
  • +
  • Address: ${escape(location.address)}
  • +
  • Start Date: ${escape(location.startDate)}
  • +
  • Year: ${escape(location.year)}
  • +
  • __v: ${escape(location.__v)}
  • ` : ``}` : ``} + `; + })}
`}`; +}); +export { + Page as default +}; diff --git a/svelte/.netlify/server/entries/pages/login/_page.server.js b/svelte/.netlify/server/entries/pages/login/_page.server.js new file mode 100644 index 0000000..1a70721 --- /dev/null +++ b/svelte/.netlify/server/entries/pages/login/_page.server.js @@ -0,0 +1,31 @@ +import { r as redirect, f as fail } from "../../../chunks/index.js"; +import { p as post } from "../../../chunks/api.js"; +async function load({ parent, url }) { + const { user } = await parent(); + if (user) + return redirect(307, "/"); + const para = url.searchParams.get("error"); + return { para }; +} +const actions = { + // @ts-ignore + default: async ({ cookies, request, locals }) => { + const data = await request.formData(); + const user = { + username: data.get("username"), + password: data.get("password") + }; + const { jwt } = await post("users/login", user); + user.username; + if (jwt.errors) { + return fail(401, jwt); + } + cookies.set("jwt", jwt, { path: "/" }); + locals.jwt = jwt; + return jwt; + } +}; +export { + actions, + load +}; diff --git a/svelte/.netlify/server/entries/pages/login/_page.svelte.js b/svelte/.netlify/server/entries/pages/login/_page.svelte.js new file mode 100644 index 0000000..32bc774 --- /dev/null +++ b/svelte/.netlify/server/entries/pages/login/_page.svelte.js @@ -0,0 +1,23 @@ +import { c as create_ssr_component } from "../../../chunks/index2.js"; +const Page = create_ssr_component(($$result, $$props, $$bindings, slots) => { + let { data } = $$props; + if ($$props.data === void 0 && $$bindings.data && data !== void 0) + $$bindings.data(data); + return `${$$result.head += `${$$result.title = `Login`, ""}`, ""} + +${data.para ? `

Incorrect Password

` : ``} + +

Login

+

If you don't have an account, go register first !

+ + +
+ +
+
`; +}); +export { + Page as default +}; diff --git a/svelte/.netlify/server/entries/pages/logout/_page.server.js b/svelte/.netlify/server/entries/pages/logout/_page.server.js new file mode 100644 index 0000000..1fb4226 --- /dev/null +++ b/svelte/.netlify/server/entries/pages/logout/_page.server.js @@ -0,0 +1,9 @@ +import { r as redirect } from "../../../chunks/index.js"; +async function load({ cookies, parent }) { + await parent(); + cookies.delete("jwt"); + throw redirect(307, "/"); +} +export { + load +}; diff --git a/svelte/.netlify/server/entries/pages/logout/_page.svelte.js b/svelte/.netlify/server/entries/pages/logout/_page.svelte.js new file mode 100644 index 0000000..d8a9b87 --- /dev/null +++ b/svelte/.netlify/server/entries/pages/logout/_page.svelte.js @@ -0,0 +1,7 @@ +import { c as create_ssr_component } from "../../../chunks/index2.js"; +const Page = create_ssr_component(($$result, $$props, $$bindings, slots) => { + return ``; +}); +export { + Page as default +}; diff --git a/svelte/.netlify/server/entries/pages/register/_page.server.js b/svelte/.netlify/server/entries/pages/register/_page.server.js new file mode 100644 index 0000000..f7160bd --- /dev/null +++ b/svelte/.netlify/server/entries/pages/register/_page.server.js @@ -0,0 +1,27 @@ +import { f as fail, r as redirect } from "../../../chunks/index.js"; +import { p as post } from "../../../chunks/api.js"; +async function load({ parent, url }) { + await parent(); + const para = url.searchParams.get("valid"); + return { para }; +} +const actions = { + // @ts-ignore + default: async ({ cookies, request }) => { + const data = await request.formData(); + const user = { + username: data.get("username"), + password: data.get("password") + }; + const body = await post("users/register", user); + if (body.errors) + return fail(401, body); + const value = btoa(JSON.stringify(body.user)); + cookies.set("jwt", value, { path: "/" }); + throw redirect(307, "/register?valid=true"); + } +}; +export { + actions, + load +}; diff --git a/svelte/.netlify/server/entries/pages/register/_page.svelte.js b/svelte/.netlify/server/entries/pages/register/_page.svelte.js new file mode 100644 index 0000000..c99edd4 --- /dev/null +++ b/svelte/.netlify/server/entries/pages/register/_page.svelte.js @@ -0,0 +1,19 @@ +import { c as create_ssr_component } from "../../../chunks/index2.js"; +import "devalue"; +const Page = create_ssr_component(($$result, $$props, $$bindings, slots) => { + let { data } = $$props; + if ($$props.data === void 0 && $$bindings.data && data !== void 0) + $$bindings.data(data); + return `${$$result.head += `${$$result.title = `Register`, ""}`, ""} + + +

Register

+
+
+ ${data.para === "true" ? `

User created

` : ``}
+
+
`; +}); +export { + Page as default +}; diff --git a/svelte/.netlify/server/index.js b/svelte/.netlify/server/index.js new file mode 100644 index 0000000..cff999b --- /dev/null +++ b/svelte/.netlify/server/index.js @@ -0,0 +1,2516 @@ +import { H as HttpError, j as json, t as text, R as Redirect, e as error, A as ActionFailure } from "./chunks/index.js"; +import * as devalue from "devalue"; +import { n as noop, s as safe_not_equal } from "./chunks/index2.js"; +import { a as assets, b as base, p as public_env, v as version, o as options, g as get_hooks, s as set_public_env } from "./chunks/internal.js"; +import { parse, serialize } from "cookie"; +import * as set_cookie_parser from "set-cookie-parser"; +const DEV = false; +function negotiate(accept, types) { + const parts = []; + accept.split(",").forEach((str, i) => { + const match = /([^/]+)\/([^;]+)(?:;q=([0-9.]+))?/.exec(str); + if (match) { + const [, type, subtype, q = "1"] = match; + parts.push({ type, subtype, q: +q, i }); + } + }); + parts.sort((a, b) => { + if (a.q !== b.q) { + return b.q - a.q; + } + if (a.subtype === "*" !== (b.subtype === "*")) { + return a.subtype === "*" ? 1 : -1; + } + if (a.type === "*" !== (b.type === "*")) { + return a.type === "*" ? 1 : -1; + } + return a.i - b.i; + }); + let accepted; + let min_priority = Infinity; + for (const mimetype of types) { + const [type, subtype] = mimetype.split("/"); + const priority = parts.findIndex( + (part) => (part.type === type || part.type === "*") && (part.subtype === subtype || part.subtype === "*") + ); + if (priority !== -1 && priority < min_priority) { + accepted = mimetype; + min_priority = priority; + } + } + return accepted; +} +function is_content_type(request, ...types) { + const type = request.headers.get("content-type")?.split(";", 1)[0].trim() ?? ""; + return types.includes(type); +} +function is_form_content_type(request) { + return is_content_type(request, "application/x-www-form-urlencoded", "multipart/form-data"); +} +function coalesce_to_error(err) { + return err instanceof Error || err && /** @type {any} */ + err.name && /** @type {any} */ + err.message ? ( + /** @type {Error} */ + err + ) : new Error(JSON.stringify(err)); +} +function normalize_error(error2) { + return ( + /** @type {Redirect | HttpError | Error} */ + error2 + ); +} +const GENERIC_ERROR = { + id: "__error" +}; +function method_not_allowed(mod, method) { + return text(`${method} method not allowed`, { + status: 405, + headers: { + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405 + // "The server must generate an Allow header field in a 405 status code response" + allow: allowed_methods(mod).join(", ") + } + }); +} +function allowed_methods(mod) { + const allowed = []; + for (const method in ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]) { + if (method in mod) + allowed.push(method); + } + if (mod.GET || mod.HEAD) + allowed.push("HEAD"); + return allowed; +} +function static_error_page(options2, status, message) { + return text(options2.templates.error({ status, message }), { + headers: { "content-type": "text/html; charset=utf-8" }, + status + }); +} +async function handle_fatal_error(event, options2, error2) { + error2 = error2 instanceof HttpError ? error2 : coalesce_to_error(error2); + const status = error2 instanceof HttpError ? error2.status : 500; + const body = await handle_error_and_jsonify(event, options2, error2); + const type = negotiate(event.request.headers.get("accept") || "text/html", [ + "application/json", + "text/html" + ]); + if (event.isDataRequest || type === "application/json") { + return json(body, { + status + }); + } + return static_error_page(options2, status, body.message); +} +async function handle_error_and_jsonify(event, options2, error2) { + if (error2 instanceof HttpError) { + return error2.body; + } else { + return await options2.hooks.handleError({ error: error2, event }) ?? { + message: event.route.id != null ? "Internal Error" : "Not Found" + }; + } +} +function redirect_response(status, location) { + const response = new Response(void 0, { + status, + headers: { location } + }); + return response; +} +function clarify_devalue_error(event, error2) { + if (error2.path) { + return `Data returned from \`load\` while rendering ${event.route.id} is not serializable: ${error2.message} (data${error2.path})`; + } + if (error2.path === "") { + return `Data returned from \`load\` while rendering ${event.route.id} is not a plain object`; + } + return error2.message; +} +function serialize_data_node(node) { + if (!node) + return "null"; + if (node.type === "error" || node.type === "skip") { + return JSON.stringify(node); + } + const stringified = devalue.stringify(node.data); + const uses = []; + if (node.uses.dependencies.size > 0) { + uses.push(`"dependencies":${JSON.stringify(Array.from(node.uses.dependencies))}`); + } + if (node.uses.params.size > 0) { + uses.push(`"params":${JSON.stringify(Array.from(node.uses.params))}`); + } + if (node.uses.parent) + uses.push(`"parent":1`); + if (node.uses.route) + uses.push(`"route":1`); + if (node.uses.url) + uses.push(`"url":1`); + return `{"type":"data","data":${stringified},"uses":{${uses.join(",")}}${node.slash ? `,"slash":${JSON.stringify(node.slash)}` : ""}}`; +} +async function render_endpoint(event, route, mod, state) { + const method = ( + /** @type {import('types').HttpMethod} */ + event.request.method + ); + let handler = mod[method]; + if (!handler && method === "HEAD") { + handler = mod.GET; + } + if (!handler) { + return method_not_allowed(mod, method); + } + const prerender = mod.prerender ?? state.prerender_default; + if (prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) { + throw new Error("Cannot prerender endpoints that have mutative methods"); + } + if (state.prerendering && !prerender) { + if (state.initiator) { + throw new Error(`${event.route.id} is not prerenderable`); + } else { + return new Response(void 0, { status: 204 }); + } + } + state.initiator = route; + try { + const response = await handler( + /** @type {import('types').RequestEvent>} */ + event + ); + if (!(response instanceof Response)) { + throw new Error( + `Invalid response from route ${event.url.pathname}: handler should return a Response object` + ); + } + if (state.prerendering) { + response.headers.set("x-sveltekit-prerender", String(prerender)); + } + return response; + } catch (e) { + if (e instanceof Redirect) { + return new Response(void 0, { + status: e.status, + headers: { location: e.location } + }); + } + throw e; + } +} +function is_endpoint_request(event) { + const { method, headers } = event.request; + if (method === "PUT" || method === "PATCH" || method === "DELETE" || method === "OPTIONS") { + return true; + } + if (method === "POST" && headers.get("x-sveltekit-action") === "true") + return false; + const accept = event.request.headers.get("accept") ?? "*/*"; + return negotiate(accept, ["*", "text/html"]) !== "text/html"; +} +function compact(arr) { + return arr.filter( + /** @returns {val is NonNullable} */ + (val) => val != null + ); +} +function normalize_path(path, trailing_slash) { + if (path === "/" || trailing_slash === "ignore") + return path; + if (trailing_slash === "never") { + return path.endsWith("/") ? path.slice(0, -1) : path; + } else if (trailing_slash === "always" && !path.endsWith("/")) { + return path + "/"; + } + return path; +} +function decode_pathname(pathname) { + return pathname.split("%25").map(decodeURI).join("%25"); +} +function decode_params(params) { + for (const key2 in params) { + params[key2] = decodeURIComponent(params[key2]); + } + return params; +} +const tracked_url_properties = ["href", "pathname", "search", "searchParams", "toString", "toJSON"]; +function make_trackable(url, callback) { + const tracked = new URL(url); + for (const property of tracked_url_properties) { + let value = tracked[property]; + Object.defineProperty(tracked, property, { + get() { + callback(); + return value; + }, + enumerable: true, + configurable: true + }); + } + { + tracked[Symbol.for("nodejs.util.inspect.custom")] = (depth, opts, inspect) => { + return inspect(url, opts); + }; + } + disable_hash(tracked); + return tracked; +} +function disable_hash(url) { + Object.defineProperty(url, "hash", { + get() { + throw new Error( + "Cannot access event.url.hash. Consider using `$page.url.hash` inside a component instead" + ); + } + }); +} +function disable_search(url) { + for (const property of ["search", "searchParams"]) { + Object.defineProperty(url, property, { + get() { + throw new Error(`Cannot access url.${property} on a page with prerendering enabled`); + } + }); + } +} +const DATA_SUFFIX = "/__data.json"; +function has_data_suffix(pathname) { + return pathname.endsWith(DATA_SUFFIX); +} +function add_data_suffix(pathname) { + return pathname.replace(/\/$/, "") + DATA_SUFFIX; +} +function strip_data_suffix(pathname) { + return pathname.slice(0, -DATA_SUFFIX.length); +} +function is_action_json_request(event) { + const accept = negotiate(event.request.headers.get("accept") ?? "*/*", [ + "application/json", + "text/html" + ]); + return accept === "application/json" && event.request.method === "POST"; +} +async function handle_action_json_request(event, options2, server) { + const actions = server?.actions; + if (!actions) { + const no_actions_error = error(405, "POST method not allowed. No actions exist for this page"); + return action_json( + { + type: "error", + error: await handle_error_and_jsonify(event, options2, no_actions_error) + }, + { + status: no_actions_error.status, + headers: { + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405 + // "The server must generate an Allow header field in a 405 status code response" + allow: "GET" + } + } + ); + } + check_named_default_separate(actions); + try { + const data = await call_action(event, actions); + if (false) + ; + if (data instanceof ActionFailure) { + return action_json({ + type: "failure", + status: data.status, + // @ts-expect-error we assign a string to what is supposed to be an object. That's ok + // because we don't use the object outside, and this way we have better code navigation + // through knowing where the related interface is used. + data: stringify_action_response( + data.data, + /** @type {string} */ + event.route.id + ) + }); + } else { + return action_json({ + type: "success", + status: data ? 200 : 204, + // @ts-expect-error see comment above + data: stringify_action_response( + data, + /** @type {string} */ + event.route.id + ) + }); + } + } catch (e) { + const err = normalize_error(e); + if (err instanceof Redirect) { + return action_json({ + type: "redirect", + status: err.status, + location: err.location + }); + } + return action_json( + { + type: "error", + error: await handle_error_and_jsonify(event, options2, check_incorrect_fail_use(err)) + }, + { + status: err instanceof HttpError ? err.status : 500 + } + ); + } +} +function check_incorrect_fail_use(error2) { + return error2 instanceof ActionFailure ? new Error(`Cannot "throw fail()". Use "return fail()"`) : error2; +} +function action_json(data, init2) { + return json(data, init2); +} +function is_action_request(event) { + return event.request.method === "POST"; +} +async function handle_action_request(event, server) { + const actions = server?.actions; + if (!actions) { + event.setHeaders({ + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405 + // "The server must generate an Allow header field in a 405 status code response" + allow: "GET" + }); + return { + type: "error", + error: error(405, "POST method not allowed. No actions exist for this page") + }; + } + check_named_default_separate(actions); + try { + const data = await call_action(event, actions); + if (false) + ; + if (data instanceof ActionFailure) { + return { + type: "failure", + status: data.status, + data: data.data + }; + } else { + return { + type: "success", + status: 200, + // @ts-expect-error this will be removed upon serialization, so `undefined` is the same as omission + data + }; + } + } catch (e) { + const err = normalize_error(e); + if (err instanceof Redirect) { + return { + type: "redirect", + status: err.status, + location: err.location + }; + } + return { + type: "error", + error: check_incorrect_fail_use(err) + }; + } +} +function check_named_default_separate(actions) { + if (actions.default && Object.keys(actions).length > 1) { + throw new Error( + `When using named actions, the default action cannot be used. See the docs for more info: https://kit.svelte.dev/docs/form-actions#named-actions` + ); + } +} +async function call_action(event, actions) { + const url = new URL(event.request.url); + let name = "default"; + for (const param of url.searchParams) { + if (param[0].startsWith("/")) { + name = param[0].slice(1); + if (name === "default") { + throw new Error('Cannot use reserved action name "default"'); + } + break; + } + } + const action = actions[name]; + if (!action) { + throw new Error(`No action with name '${name}' found`); + } + if (!is_form_content_type(event.request)) { + throw new Error( + `Actions expect form-encoded data (received ${event.request.headers.get("content-type")}` + ); + } + return action(event); +} +function validate_action_return(data) { + if (data instanceof Redirect) { + throw new Error(`Cannot \`return redirect(...)\` — use \`throw redirect(...)\` instead`); + } + if (data instanceof HttpError) { + throw new Error( + `Cannot \`return error(...)\` — use \`throw error(...)\` or \`return fail(...)\` instead` + ); + } +} +function uneval_action_response(data, route_id) { + return try_deserialize(data, devalue.uneval, route_id); +} +function stringify_action_response(data, route_id) { + return try_deserialize(data, devalue.stringify, route_id); +} +function try_deserialize(data, fn, route_id) { + try { + return fn(data); + } catch (e) { + const error2 = ( + /** @type {any} */ + e + ); + if ("path" in error2) { + let message = `Data returned from action inside ${route_id} is not serializable: ${error2.message}`; + if (error2.path !== "") + message += ` (data.${error2.path})`; + throw new Error(message); + } + throw error2; + } +} +async function unwrap_promises(object) { + for (const key2 in object) { + if (typeof object[key2]?.then === "function") { + return Object.fromEntries( + await Promise.all(Object.entries(object).map(async ([key3, value]) => [key3, await value])) + ); + } + } + return object; +} +async function load_server_data({ event, state, node, parent }) { + if (!node?.server) + return null; + const uses = { + dependencies: /* @__PURE__ */ new Set(), + params: /* @__PURE__ */ new Set(), + parent: false, + route: false, + url: false + }; + const url = make_trackable(event.url, () => { + uses.url = true; + }); + if (state.prerendering) { + disable_search(url); + } + const result = await node.server.load?.call(null, { + ...event, + fetch: (info, init2) => { + const url2 = new URL(info instanceof Request ? info.url : info, event.url); + uses.dependencies.add(url2.href); + return event.fetch(info, init2); + }, + /** @param {string[]} deps */ + depends: (...deps) => { + for (const dep of deps) { + const { href } = new URL(dep, event.url); + uses.dependencies.add(href); + } + }, + params: new Proxy(event.params, { + get: (target, key2) => { + uses.params.add(key2); + return target[ + /** @type {string} */ + key2 + ]; + } + }), + parent: async () => { + uses.parent = true; + return parent(); + }, + route: { + get id() { + uses.route = true; + return event.route.id; + } + }, + url + }); + const data = result ? await unwrap_promises(result) : null; + return { + type: "data", + data, + uses, + slash: node.server.trailingSlash + }; +} +async function load_data({ + event, + fetched, + node, + parent, + server_data_promise, + state, + resolve_opts, + csr +}) { + const server_data_node = await server_data_promise; + if (!node?.universal?.load) { + return server_data_node?.data ?? null; + } + const result = await node.universal.load.call(null, { + url: event.url, + params: event.params, + data: server_data_node?.data ?? null, + route: event.route, + fetch: create_universal_fetch(event, state, fetched, csr, resolve_opts), + setHeaders: event.setHeaders, + depends: () => { + }, + parent + }); + const data = result ? await unwrap_promises(result) : null; + validate_load_response( + data, + /** @type {string} */ + event.route.id + ); + return data; +} +function create_universal_fetch(event, state, fetched, csr, resolve_opts) { + return async (input, init2) => { + const cloned_body = input instanceof Request && input.body ? input.clone().body : null; + let response = await event.fetch(input, init2); + const url = new URL(input instanceof Request ? input.url : input, event.url); + const same_origin = url.origin === event.url.origin; + let dependency; + if (same_origin) { + if (state.prerendering) { + dependency = { response, body: null }; + state.prerendering.dependencies.set(url.pathname, dependency); + } + } else { + const mode = input instanceof Request ? input.mode : init2?.mode ?? "cors"; + if (mode === "no-cors") { + response = new Response("", { + status: response.status, + statusText: response.statusText, + headers: response.headers + }); + } else { + const acao = response.headers.get("access-control-allow-origin"); + if (!acao || acao !== event.url.origin && acao !== "*") { + throw new Error( + `CORS error: ${acao ? "Incorrect" : "No"} 'Access-Control-Allow-Origin' header is present on the requested resource` + ); + } + } + } + const proxy = new Proxy(response, { + get(response2, key2, _receiver) { + async function text2() { + const body = await response2.text(); + if (!body || typeof body === "string") { + const status_number = Number(response2.status); + if (isNaN(status_number)) { + throw new Error( + `response.status is not a number. value: "${response2.status}" type: ${typeof response2.status}` + ); + } + fetched.push({ + url: same_origin ? url.href.slice(event.url.origin.length) : url.href, + method: event.request.method, + request_body: ( + /** @type {string | ArrayBufferView | undefined} */ + input instanceof Request && cloned_body ? await stream_to_string(cloned_body) : init2?.body + ), + request_headers: init2?.headers, + response_body: body, + response: response2 + }); + } + if (dependency) { + dependency.body = body; + } + return body; + } + if (key2 === "arrayBuffer") { + return async () => { + const buffer = await response2.arrayBuffer(); + if (dependency) { + dependency.body = new Uint8Array(buffer); + } + return buffer; + }; + } + if (key2 === "text") { + return text2; + } + if (key2 === "json") { + return async () => { + return JSON.parse(await text2()); + }; + } + return Reflect.get(response2, key2, response2); + } + }); + if (csr) { + const get = response.headers.get; + response.headers.get = (key2) => { + const lower = key2.toLowerCase(); + const value = get.call(response.headers, lower); + if (value && !lower.startsWith("x-sveltekit-")) { + const included = resolve_opts.filterSerializedResponseHeaders(lower, value); + if (!included) { + throw new Error( + `Failed to get response header "${lower}" — it must be included by the \`filterSerializedResponseHeaders\` option: https://kit.svelte.dev/docs/hooks#server-hooks-handle (at ${event.route.id})` + ); + } + } + return value; + }; + } + return proxy; + }; +} +async function stream_to_string(stream) { + let result = ""; + const reader = stream.getReader(); + const decoder = new TextDecoder(); + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; + } + result += decoder.decode(value); + } + return result; +} +function validate_load_response(data, routeId) { + if (data != null && Object.getPrototypeOf(data) !== Object.prototype) { + throw new Error( + `a load function related to route '${routeId}' returned ${typeof data !== "object" ? `a ${typeof data}` : data instanceof Response ? "a Response object" : Array.isArray(data) ? "an array" : "a non-plain object"}, but must return a plain object at the top level (i.e. \`return {...}\`)` + ); + } +} +const subscriber_queue = []; +function readable(value, start) { + return { + subscribe: writable(value, start).subscribe + }; +} +function writable(value, start = noop) { + let stop; + const subscribers = /* @__PURE__ */ new Set(); + function set(new_value) { + if (safe_not_equal(value, new_value)) { + value = new_value; + if (stop) { + const run_queue = !subscriber_queue.length; + for (const subscriber of subscribers) { + subscriber[1](); + subscriber_queue.push(subscriber, value); + } + if (run_queue) { + for (let i = 0; i < subscriber_queue.length; i += 2) { + subscriber_queue[i][0](subscriber_queue[i + 1]); + } + subscriber_queue.length = 0; + } + } + } + } + function update(fn) { + set(fn(value)); + } + function subscribe(run, invalidate = noop) { + const subscriber = [run, invalidate]; + subscribers.add(subscriber); + if (subscribers.size === 1) { + stop = start(set) || noop; + } + run(value); + return () => { + subscribers.delete(subscriber); + if (subscribers.size === 0) { + stop(); + stop = null; + } + }; + } + return { set, update, subscribe }; +} +function hash(...values) { + let hash2 = 5381; + for (const value of values) { + if (typeof value === "string") { + let i = value.length; + while (i) + hash2 = hash2 * 33 ^ value.charCodeAt(--i); + } else if (ArrayBuffer.isView(value)) { + const buffer = new Uint8Array(value.buffer, value.byteOffset, value.byteLength); + let i = buffer.length; + while (i) + hash2 = hash2 * 33 ^ buffer[--i]; + } else { + throw new TypeError("value must be a string or TypedArray"); + } + } + return (hash2 >>> 0).toString(36); +} +const escape_html_attr_dict = { + "&": "&", + '"': """ +}; +const escape_html_attr_regex = new RegExp( + // special characters + `[${Object.keys(escape_html_attr_dict).join("")}]|[\\ud800-\\udbff](?![\\udc00-\\udfff])|[\\ud800-\\udbff][\\udc00-\\udfff]|[\\udc00-\\udfff]`, + "g" +); +function escape_html_attr(str) { + const escaped_str = str.replace(escape_html_attr_regex, (match) => { + if (match.length === 2) { + return match; + } + return escape_html_attr_dict[match] ?? `&#${match.charCodeAt(0)};`; + }); + return `"${escaped_str}"`; +} +const replacements = { + "<": "\\u003C", + "\u2028": "\\u2028", + "\u2029": "\\u2029" +}; +const pattern = new RegExp(`[${Object.keys(replacements).join("")}]`, "g"); +function serialize_data(fetched, filter, prerendering = false) { + const headers = {}; + let cache_control = null; + let age = null; + let vary = false; + for (const [key2, value] of fetched.response.headers) { + if (filter(key2, value)) { + headers[key2] = value; + } + if (key2 === "cache-control") + cache_control = value; + if (key2 === "age") + age = value; + if (key2 === "vary") + vary = true; + } + const payload = { + status: fetched.response.status, + statusText: fetched.response.statusText, + headers, + body: fetched.response_body + }; + const safe_payload = JSON.stringify(payload).replace(pattern, (match) => replacements[match]); + const attrs = [ + 'type="application/json"', + "data-sveltekit-fetched", + `data-url=${escape_html_attr(fetched.url)}` + ]; + if (fetched.request_headers || fetched.request_body) { + const values = []; + if (fetched.request_headers) { + values.push([...new Headers(fetched.request_headers)].join(",")); + } + if (fetched.request_body) { + values.push(fetched.request_body); + } + attrs.push(`data-hash="${hash(...values)}"`); + } + if (!prerendering && fetched.method === "GET" && cache_control && !vary) { + const match = /s-maxage=(\d+)/g.exec(cache_control) ?? /max-age=(\d+)/g.exec(cache_control); + if (match) { + const ttl = +match[1] - +(age ?? "0"); + attrs.push(`data-ttl="${ttl}"`); + } + } + return ` + +
+
+ +
+ +
+ + +
+ + diff --git a/svelte/src/routes/+page.server.js b/svelte/src/routes/+page.server.js new file mode 100644 index 0000000..b907065 --- /dev/null +++ b/svelte/src/routes/+page.server.js @@ -0,0 +1,3 @@ +export async function load({parent }) { + const { user } = await parent(); +} \ No newline at end of file diff --git a/svelte/src/routes/+page.svelte b/svelte/src/routes/+page.svelte new file mode 100644 index 0000000..dd4a7c5 --- /dev/null +++ b/svelte/src/routes/+page.svelte @@ -0,0 +1,27 @@ + + + + Home + + + +
+

+ Welcome on this website !!! +

+
+ + diff --git a/svelte/src/routes/Header.svelte b/svelte/src/routes/Header.svelte new file mode 100644 index 0000000..26d6ef8 --- /dev/null +++ b/svelte/src/routes/Header.svelte @@ -0,0 +1,95 @@ + + +
+
+
+ + + +
+
+
+ + diff --git a/svelte/src/routes/add/+page.server.js b/svelte/src/routes/add/+page.server.js new file mode 100644 index 0000000..e12c233 --- /dev/null +++ b/svelte/src/routes/add/+page.server.js @@ -0,0 +1,50 @@ +import { redirect, fail } from '@sveltejs/kit'; +import * as api from '$lib/api.js'; + + +export async function load({ locals,url }) { + let jwt = locals.jwt + if(jwt==null) + { + throw redirect(307, '/login'); + } + let role = JSON.parse(atob(jwt.split('.')[1])).role; + + if(role!='admin'){ + throw redirect(307, '/locations'); + } + let para = url.searchParams.get('success') + return {para} +} + + +export const actions = { + default: async ({request,locals }) => { + + const data = await request.formData(); + + const user = { + filmType : data.get('filmType'), + filmProducerName : data.get('filmProducerName'), + endDate : data.get('endDate'), + filmName : data.get('filmName'), + district : data.get('district'), + sourceLocationId : data.get('sourceLocationId'), + filmDirectorName : data.get('filmDirectorName'), + address : data.get('address'), + startDate : data.get('startDate'), + year : data.get('year'), + }; + + const body = await api.post('locations/', user,locals.jwt); + + if (body.errors) { + return fail(401, body); + } + throw redirect(307, '/add?success=true'); + } +}; + + + + diff --git a/svelte/src/routes/add/+page.svelte b/svelte/src/routes/add/+page.svelte new file mode 100644 index 0000000..bcd0f78 --- /dev/null +++ b/svelte/src/routes/add/+page.svelte @@ -0,0 +1,50 @@ + + + + + New location + + + +

Add a Location

+ {#if data.para} +

Done !

+ {/if} +
+
+ + + + + + + + + +
+ +
+
+ + + + + \ No newline at end of file diff --git a/svelte/src/routes/locations/+page.server.js b/svelte/src/routes/locations/+page.server.js new file mode 100644 index 0000000..4053ef6 --- /dev/null +++ b/svelte/src/routes/locations/+page.server.js @@ -0,0 +1,40 @@ +import { redirect, fail } from '@sveltejs/kit'; +import * as api from '$lib/api.js'; + + +export async function load({ locals, request, response }) { + let jwt = locals.jwt; + let body = await api.get('locations', jwt); + return { body, jwt }; +} + +export const actions = { + default: async ({ request, locals }) => { + const data = await request.formData(); + + const user = { + _id: data.get('_id'), + filmType: data.get('filmType'), + filmProducerName: data.get('filmProducerName'), + endDate: data.get('endDate'), + filmName: data.get('filmName'), + district: data.get('district'), + coordinates: data.get('coordinates'), + sourceLocationId: data.get('sourceLocationId'), + filmDirectorName: data.get('filmDirectorName'), + address: data.get('address'), + startDate: data.get('startDate'), + year: data.get('year'), + __v: data.get('__v'), + }; + + + const body = await api.patch(`locations/${user._id}`, user, locals.jwt); + //on redirige pour update directement les elements + throw redirect(307, '/locations'); + + if (body.errors) { + return fail(401, body); + } + }, +}; diff --git a/svelte/src/routes/locations/+page.svelte b/svelte/src/routes/locations/+page.svelte new file mode 100644 index 0000000..1cfb347 --- /dev/null +++ b/svelte/src/routes/locations/+page.svelte @@ -0,0 +1,115 @@ + + + + Locations + + + +

Locations in Paris (click on the locations)

+ + {#if role=='admin'} + +

Don't hesitate to modify the locations

+ {/if} + + {#if locations.length === 0} +

Loading...

+ {:else} +
    + {#each locations as location} +
  • +

    {location.showDetails = !location.showDetails}}>{location.filmName} - ID : {location._id}

    + {#if location.showDetails} + {#if role=="admin"} +
    +
    + + + + + + + + + + + + +
    + +
    +
    +
    + +
    +
    + {/if} + {#if role!="admin"} +
    +
  • ID : {location._id}
  • +
  • Film Type : {location.filmType}
  • +
  • Film Producer Name : {location.filmProducerName}
  • +
  • End Date : {location.endDate}
  • +
  • Film Name : {location.filmName}
  • +
  • District : {location.district}
  • +
  • Geolocation : {location.geolocation}
  • +
  • Coordinates : {location.coordinates}
  • +
  • Type : {location.type}
  • +
  • Source Location ID : {location.sourceLocationId}
  • +
  • Film Director Name : {location.filmDirectorName}
  • +
  • Address : {location.address}
  • +
  • Start Date : {location.startDate}
  • +
  • Year : {location.year}
  • + + {/if} + {/if} + + {/each} +
+ {/if} + + + \ No newline at end of file diff --git a/svelte/src/routes/login/+page.server.js b/svelte/src/routes/login/+page.server.js new file mode 100644 index 0000000..144a52c --- /dev/null +++ b/svelte/src/routes/login/+page.server.js @@ -0,0 +1,29 @@ +import { fail, redirect } from '@sveltejs/kit'; +import * as api from '$lib/api.js'; + +export async function load({ parent, url }) { + const { user } = await parent(); + if (user) return redirect(307, '/'); + const para = url.searchParams.get('error'); + return { para }; +} + + +export const actions = { + default: async ({ cookies, request,locals}) => { + const data = await request.formData(); + const user = { + username: data.get('username'), + password: data.get('password'), + }; + + const { jwt } = await api.post('users/login', user); + if (jwt.errors) { + return fail(401, jwt); + } + + cookies.set('jwt', jwt, { path: '/' }); + locals.jwt = jwt; + return jwt; + }, +}; diff --git a/svelte/src/routes/login/+page.svelte b/svelte/src/routes/login/+page.svelte new file mode 100644 index 0000000..1cccf2c --- /dev/null +++ b/svelte/src/routes/login/+page.svelte @@ -0,0 +1,70 @@ + + + + Login + + + + + {#if data.para} +

Incorrect password !

+ {/if} + +
+

Login

+

If you don't have a account yet, you have to create one first !

+
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+ + + + + + + + diff --git a/svelte/src/routes/logout/+page.server.js b/svelte/src/routes/logout/+page.server.js new file mode 100644 index 0000000..886ef2d --- /dev/null +++ b/svelte/src/routes/logout/+page.server.js @@ -0,0 +1,7 @@ +import {redirect} from "@sveltejs/kit"; + +export async function load({ cookies,parent }) { + const { user } = await parent(); + cookies.delete("jwt") + throw redirect(307, '/'); +} \ No newline at end of file diff --git a/svelte/src/routes/logout/+page.svelte b/svelte/src/routes/logout/+page.svelte new file mode 100644 index 0000000..e69de29 diff --git a/svelte/src/routes/register/+page.server.js b/svelte/src/routes/register/+page.server.js new file mode 100644 index 0000000..af608af --- /dev/null +++ b/svelte/src/routes/register/+page.server.js @@ -0,0 +1,26 @@ +import { fail, redirect } from '@sveltejs/kit'; +import * as api from '$lib/api.js'; + +export async function load({ parent, url }) { + const { user } = await parent(); + const para = url.searchParams.get('valid'); + return { para }; +} + +export const actions = { + default: async ({ cookies, request }) => { + const data = await request.formData(); + const user = { + username: data.get('username'), + password: data.get('password'), + }; + + const body = await api.post('users/register', user); + if (body.errors) return fail(401, body); + + const value = btoa(JSON.stringify(body.user)); + cookies.set('jwt', value, { path: '/' }); + + throw redirect(307, '/register?valid=true'); + }, +}; diff --git a/svelte/src/routes/register/+page.svelte b/svelte/src/routes/register/+page.svelte new file mode 100644 index 0000000..482dd44 --- /dev/null +++ b/svelte/src/routes/register/+page.svelte @@ -0,0 +1,55 @@ + + + + Register + + + +

Register

+
+
+
+ +
+
+ +
+ {#if data.para === 'true'} +

User created

+ {/if} +
+
+
+
+
+
+ + diff --git a/svelte/src/routes/stylehead.css b/svelte/src/routes/stylehead.css new file mode 100644 index 0000000..dfcb249 --- /dev/null +++ b/svelte/src/routes/stylehead.css @@ -0,0 +1,73 @@ +header { + display: flex; + justify-content: space-between; +} + +.corner { + width: 3em; + height: 3em; +} + +nav { + display: flex; + justify-content: center; + --background: rgba(255, 255, 255, 0.7); +} + +svg { + width: 2em; + height: 3em; + display: block; +} + +path { + fill: var(--background); +} + +ul { + position: relative; + padding: 0; + margin: 0; + height: 3em; + display: flex; + justify-content: center; + align-items: center; + list-style: none; + background: var(--background); + background-size: contain; +} + +li { + position: relative; + height: 100%; +} + +li[aria-current='page']::before { + --size: 6px; + content: ''; + width: 0; + height: 0; + position: absolute; + top: 0; + left: calc(50% - var(--size)); + border: var(--size) solid transparent; + border-top: var(--size) solid var(--color-theme-1); +} + +nav a { + display: flex; + height: 100%; + align-items: center; + padding: 0 0.5rem; + color: var(--color-text); + font-weight: 700; + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.1em; + text-decoration: none; + transition: color 0.2s linear; +} + +a:hover { + color: var(--color-theme-1); +} \ No newline at end of file diff --git a/svelte/src/routes/styles.css b/svelte/src/routes/styles.css new file mode 100644 index 0000000..a915d3c --- /dev/null +++ b/svelte/src/routes/styles.css @@ -0,0 +1,96 @@ +@import 'http://github.com/@fontsource/fira-mono'; + +:root { + --font-body: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, + Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + --font-mono: 'Fira Mono', monospace; + --color-bg-0: rgb(3, 0, 28); + --color-bg-1: hsl(246, 100%, 5%); + --color-bg-2: hsl(246, 100%, 5%); + --color-theme-1: #d800a6; + --color-theme-2: #5b8fb9; + --color-text: rgb(255, 255, 255); + --column-width: 42rem; + --column-margin-top: 4rem; + font-family: var(--font-body); + color: var(--color-text); +} + +body { + margin: 0; + background-attachment: fixed; + background-color: var(--color-bg-1); +} + +h1, +h2, +p { + font-weight: 400; +} + +p { + line-height: 1.5; +} + +a { + color: var(--color-theme-1); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +h1 { + font-size: 2rem; + text-align: center; +} + +h2 { + font-size: 1rem; +} + +pre { + font-size: 16px; + font-family: var(--font-mono); + border-radius: 3px; + padding: 0.5em; + overflow-x: auto; +} + +.text-column { + display: flex; + max-width: 48rem; + flex: 0.6; + flex-direction: column; + justify-content: center; + margin: 0 auto; +} + +input, +button { + font-size: inherit; + font-family: inherit; +} + +button:focus:not(:focus-visible) { + outline: none; +} + +@media (min-width: 720px) { + h1 { + font-size: 2.4rem; + } +} + +.visually-hidden { + border: 0; + clip: rect(0 0 0 0); + height: auto; + margin: 0; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + white-space: nowrap; +} \ No newline at end of file diff --git a/svelte/static/logo.png b/svelte/static/logo.png new file mode 100644 index 0000000..4db3516 Binary files /dev/null and b/svelte/static/logo.png differ diff --git a/svelte/svelte.config.js b/svelte/svelte.config.js new file mode 100644 index 0000000..63ef04c --- /dev/null +++ b/svelte/svelte.config.js @@ -0,0 +1,10 @@ +import adapter from '@sveltejs/adapter-netlify'; + +export default { + kit: { + adapter: adapter({ + edge: false, + split: false + }) + } +}; \ No newline at end of file diff --git a/svelte/vite.config.js b/svelte/vite.config.js new file mode 100644 index 0000000..37b6a84 --- /dev/null +++ b/svelte/vite.config.js @@ -0,0 +1,9 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [sveltekit()], + test: { + include: ['src/**/*.{test,spec}.{js,ts}'] + } +});