Compare commits
No commits in common. "master" and "note-repo" have entirely different histories.
|
|
@ -1,13 +1,4 @@
|
||||||
# 🚀 Getting started with PomeloNote
|
# 🚀 Getting started with PomeloNote
|
||||||
### **THIS REPOSITORY HAS DEPENDENCIES WITH SECURITY VULNERABILITIES. YOU MIGHT WANT TO UPDATE PACKAGES BEFORE USE.**
|
|
||||||
## Setup
|
|
||||||
- run `npm i`
|
|
||||||
- get the .env file and save it to the root directory of the project
|
|
||||||
- set up Strapi
|
|
||||||
- go to `localhost:1337/admin`
|
|
||||||
- register an admin user
|
|
||||||
- go to Settings => Users&Permissions Plugin => Roles => Authenticated => Note => Select all
|
|
||||||
- Save
|
|
||||||
|
|
||||||
### Starting the container with svelte and strapi:
|
### Starting the container with svelte and strapi:
|
||||||
``docker-compose up --build -d``
|
``docker-compose up --build -d``
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,6 @@ module.exports = createCoreController(noteUid, ({strapi}) => ({
|
||||||
lastViewed: Date.now()
|
lastViewed: Date.now()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
entry = await strapi.entityService.findOne(noteUid, noteId, {
|
|
||||||
populate: ['owners'],
|
|
||||||
});
|
|
||||||
return JSON.stringify(entry);
|
return JSON.stringify(entry);
|
||||||
} else {
|
} else {
|
||||||
ctx.response.status = 403;
|
ctx.response.status = 403;
|
||||||
|
|
@ -66,26 +63,22 @@ module.exports = createCoreController(noteUid, ({strapi}) => ({
|
||||||
async update(ctx) {
|
async update(ctx) {
|
||||||
const noteId = getNoteIdFromUrl(ctx.request.url)
|
const noteId = getNoteIdFromUrl(ctx.request.url)
|
||||||
const userId = ctx.state.user.id;
|
const userId = ctx.state.user.id;
|
||||||
const requestBody = JSON.parse(ctx.request.body);
|
const requestBody = ctx.request.body;
|
||||||
console.log(JSON.stringify(requestBody, null, 2))
|
|
||||||
const entry = await strapi.entityService.findOne(noteUid, noteId, {
|
const entry = await strapi.entityService.findOne(noteUid, noteId, {
|
||||||
populate: ['owners'],
|
populate: ['owners'],
|
||||||
});
|
});
|
||||||
const authorized = entry.owners.some(owner => owner.id === userId)
|
const authorized = entry.owners.some(owner => owner.id === userId)
|
||||||
let allPreviousOwnersKept = true;
|
let allPreviousOwnersKept = false;
|
||||||
if (requestBody.data.hasOwnProperty("owners")) {
|
if (requestBody.data.hasOwnProperty("owners")) {
|
||||||
allPreviousOwnersKept = entry.owners.every(owner => requestBody.data.owners.includes(owner));
|
allPreviousOwnersKept = entry.owners.every(owner => requestBody.data.owners.includes(owner));
|
||||||
}
|
}
|
||||||
console.log({
|
|
||||||
"auth": authorized,
|
|
||||||
"allprev": allPreviousOwnersKept,
|
|
||||||
})
|
|
||||||
if (!authorized) {
|
if (!authorized) {
|
||||||
ctx.response.status = 403;
|
ctx.response.status = 403;
|
||||||
} else if (!allPreviousOwnersKept) {
|
} else if (!allPreviousOwnersKept) {
|
||||||
ctx.response.status = 400;
|
ctx.response.status = 400;
|
||||||
|
} else {
|
||||||
|
return super.update(ctx);
|
||||||
}
|
}
|
||||||
return await strapi.entityService.update(noteUid, noteId, requestBody);
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Creates a new note, automatically sets owners to the user making the request and lastViewed
|
* Creates a new note, automatically sets owners to the user making the request and lastViewed
|
||||||
|
|
@ -94,8 +87,7 @@ module.exports = createCoreController(noteUid, ({strapi}) => ({
|
||||||
*/
|
*/
|
||||||
async create(ctx) {
|
async create(ctx) {
|
||||||
const userId = ctx.state.user.id;
|
const userId = ctx.state.user.id;
|
||||||
const requestBody = JSON.parse(ctx.request.body);
|
const requestBody = ctx.request.body;
|
||||||
console.log(requestBody);
|
|
||||||
const response = await strapi.entityService.create(noteUid, {
|
const response = await strapi.entityService.create(noteUid, {
|
||||||
data: {
|
data: {
|
||||||
title: requestBody.data.title,
|
title: requestBody.data.title,
|
||||||
|
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
{
|
|
||||||
"kind": "collectionType",
|
|
||||||
"collectionName": "up_users",
|
|
||||||
"info": {
|
|
||||||
"name": "user",
|
|
||||||
"description": "",
|
|
||||||
"singularName": "user",
|
|
||||||
"pluralName": "users",
|
|
||||||
"displayName": "User"
|
|
||||||
},
|
|
||||||
"options": {
|
|
||||||
"draftAndPublish": false,
|
|
||||||
"timestamps": true
|
|
||||||
},
|
|
||||||
"attributes": {
|
|
||||||
"username": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 3,
|
|
||||||
"unique": true,
|
|
||||||
"configurable": false,
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"type": "email",
|
|
||||||
"minLength": 6,
|
|
||||||
"configurable": false,
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"provider": {
|
|
||||||
"type": "string",
|
|
||||||
"configurable": false
|
|
||||||
},
|
|
||||||
"password": {
|
|
||||||
"type": "password",
|
|
||||||
"minLength": 6,
|
|
||||||
"configurable": false,
|
|
||||||
"private": true
|
|
||||||
},
|
|
||||||
"resetPasswordToken": {
|
|
||||||
"type": "string",
|
|
||||||
"configurable": false,
|
|
||||||
"private": true
|
|
||||||
},
|
|
||||||
"confirmationToken": {
|
|
||||||
"type": "string",
|
|
||||||
"configurable": false,
|
|
||||||
"private": true
|
|
||||||
},
|
|
||||||
"confirmed": {
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false,
|
|
||||||
"configurable": false
|
|
||||||
},
|
|
||||||
"blocked": {
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false,
|
|
||||||
"configurable": false
|
|
||||||
},
|
|
||||||
"role": {
|
|
||||||
"type": "relation",
|
|
||||||
"relation": "manyToOne",
|
|
||||||
"target": "plugin::users-permissions.role",
|
|
||||||
"inversedBy": "users",
|
|
||||||
"configurable": false
|
|
||||||
},
|
|
||||||
"notes": {
|
|
||||||
"type": "relation",
|
|
||||||
"relation": "manyToMany",
|
|
||||||
"target": "api::note.note",
|
|
||||||
"inversedBy": "owners"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
remote_theme: pages-themes/leap-day@v0.2.0
|
|
||||||
plugins:
|
|
||||||
- jekyll-remote-theme
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
name,github,image
|
|
||||||
Jonas Weissengruber,j-weissen,jowei
|
|
||||||
Stefan Prechtler,s-prechtl,stef
|
|
||||||
David Hain,d-hain,dave
|
|
||||||
|
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 134 KiB |
|
|
@ -1,31 +0,0 @@
|
||||||
# Pomelo Note
|
|
||||||
|
|
||||||
This is the best open source note app you will ever find.
|
|
||||||
|
|
||||||
## Login
|
|
||||||
When first entering the app, you will need to login. If you haven't got an account you may consider [registering](#register), or just not using the app at all.
|
|
||||||
<br /><img src="images/login.png" width="50%" style="margin: auto; display: block;" />
|
|
||||||
|
|
||||||
## Register
|
|
||||||
A username, an email and a password that's all you need. If you are missing one of those, just don't use the app at all.
|
|
||||||
<br /><img src="images/register.png" width="50%" style="margin: auto; display: block;" />
|
|
||||||
|
|
||||||
## Editor
|
|
||||||
You can edit your notes with our minimalistic editor interface.
|
|
||||||
<br /><img src="images/editor.png" width="50%" style="margin: auto; display: block;" />
|
|
||||||
|
|
||||||
## Listing
|
|
||||||
Here you can see all your notes. Click on them to open the editor or hover and press the red "X" to delete them.
|
|
||||||
<br /><img src="images/listing.png" width="50%" style="margin: auto; display: block;" />
|
|
||||||
|
|
||||||
## Delete
|
|
||||||
Confirm the deletion.
|
|
||||||
<br /><img src="images/delete.png" width="50%" style="margin: auto; display: block;" />
|
|
||||||
|
|
||||||
# The Team
|
|
||||||
{% for dev in site.data.devs %}
|
|
||||||
{{ dev.name }}
|
|
||||||
[GitHub](https://github.com/{{ dev.github }})
|
|
||||||

|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
27
frontend/svelte/package-lock.json
generated
|
|
@ -9,9 +9,7 @@
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap-icons": "^1.9.1",
|
"bootstrap-icons": "^1.9.1",
|
||||||
"nookies": "^2.5.2",
|
"nookies": "^2.5.2"
|
||||||
"sv-popup": "^0.2.5",
|
|
||||||
"webworker": "^0.8.4"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "next",
|
"@sveltejs/adapter-auto": "next",
|
||||||
|
|
@ -2085,11 +2083,6 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sv-popup": {
|
|
||||||
"version": "0.2.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/sv-popup/-/sv-popup-0.2.5.tgz",
|
|
||||||
"integrity": "sha512-JhBu4piXaauamT4vMEcFCydvxJ8e72G7c9F3caZVAPsiFqWPTYT3JDz99FlR+YCnbOp1emsZqqOPVvCwHgURog=="
|
|
||||||
},
|
|
||||||
"node_modules/svelte": {
|
"node_modules/svelte": {
|
||||||
"version": "3.50.1",
|
"version": "3.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.50.1.tgz",
|
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.50.1.tgz",
|
||||||
|
|
@ -2365,14 +2358,6 @@
|
||||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/webworker": {
|
|
||||||
"version": "0.8.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/webworker/-/webworker-0.8.4.tgz",
|
|
||||||
"integrity": "sha512-zzsVxtHf+mCn0WuYLarSWfRGmX7JiYKkKvso5FYC7rJ9G8svwGQA5a51Sjq9D2c/rKVU6U/kyBcaI7gUTVlsJg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/whatwg-url": {
|
"node_modules/whatwg-url": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
|
@ -3839,11 +3824,6 @@
|
||||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"sv-popup": {
|
|
||||||
"version": "0.2.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/sv-popup/-/sv-popup-0.2.5.tgz",
|
|
||||||
"integrity": "sha512-JhBu4piXaauamT4vMEcFCydvxJ8e72G7c9F3caZVAPsiFqWPTYT3JDz99FlR+YCnbOp1emsZqqOPVvCwHgURog=="
|
|
||||||
},
|
|
||||||
"svelte": {
|
"svelte": {
|
||||||
"version": "3.50.1",
|
"version": "3.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.50.1.tgz",
|
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.50.1.tgz",
|
||||||
|
|
@ -4001,11 +3981,6 @@
|
||||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"webworker": {
|
|
||||||
"version": "0.8.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/webworker/-/webworker-0.8.4.tgz",
|
|
||||||
"integrity": "sha512-zzsVxtHf+mCn0WuYLarSWfRGmX7JiYKkKvso5FYC7rJ9G8svwGQA5a51Sjq9D2c/rKVU6U/kyBcaI7gUTVlsJg=="
|
|
||||||
},
|
|
||||||
"whatwg-url": {
|
"whatwg-url": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,7 @@
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap-icons": "^1.9.1",
|
|
||||||
"nookies": "^2.5.2",
|
"nookies": "^2.5.2",
|
||||||
"sv-popup": "^0.2.5",
|
"bootstrap-icons": "^1.9.1"
|
||||||
"webworker": "^0.8.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" data-theme="emerald">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="../src/resources/images/logo2.svg" />
|
<link rel="icon" href="../src/resources/images/logo2.svg" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<link rel="manifest" href="../static/manifest.json">
|
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div>%sveltekit.body%</div>
|
<div>%sveltekit.body%</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
|
|
||||||
html,
|
|
||||||
:root {
|
|
||||||
--main-txt-color: black;
|
|
||||||
--cross-txt-color: red;
|
|
||||||
|
|
||||||
--color-primary: #fff494;
|
|
||||||
--color-primary-600: #fff17a;
|
|
||||||
--color-primary-700: #ffec47;
|
|
||||||
--color-primary-800: #ffe714;
|
|
||||||
--color-primary-900: #e0c900;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background-color: var(--color-primary-800) !important;
|
|
||||||
border: var(--color-primary-800) !important;
|
|
||||||
color: var(--main-txt-color) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
background-color: var(--color-primary-900) !important;
|
|
||||||
border: var(--color-primary-900) !important;
|
|
||||||
color: var(--main-txt-color) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:disabled {
|
|
||||||
background-color: var(--color-primary-700) !important;
|
|
||||||
border: var(--color-primary-700) !important;
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type {Note} from "../../types";
|
import type {Note} from "./types";
|
||||||
|
|
||||||
export interface NoteRepository {
|
export interface NoteRepository {
|
||||||
getNotes(): Promise<Note[]>;
|
getNotes(): Promise<Note[]>;
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import type {Authentication} from "./authentication";
|
import {parseCookies} from "nookies";
|
||||||
import {createErrorToast} from "./customToasts";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Capitalises first letter of string.
|
* Capitalises first letter of string.
|
||||||
|
|
@ -23,14 +22,13 @@ export async function bearerFetch(endpoint: string, jwt: string, baseUrl: string
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleErrorsFromResponseWithToast(response: Authentication) {
|
|
||||||
if (response.error != null) {
|
const getJwtCookie = () => {
|
||||||
if (response.error.details.errors) {
|
// @ts-ignore
|
||||||
for (const error of response.error.details.errors) {
|
return parseCookies("/").jwt;
|
||||||
createErrorToast(error.message);
|
};
|
||||||
}
|
|
||||||
} else {
|
/**
|
||||||
createErrorToast(response.error.message);
|
* JWT Cookie
|
||||||
}
|
*/
|
||||||
}
|
export const jwt: string = getJwtCookie();
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import type {Note} from "../../types";
|
import type {Note} from "./types";
|
||||||
import {parseCookies} from "nookies";
|
import {parseCookies} from "nookies";
|
||||||
import type {NoteRepository} from "./NoteRepository";
|
import type {NoteRepository} from "./NoteRepository";
|
||||||
import {currentNoteId} from "../../../stores";
|
|
||||||
|
|
||||||
|
|
||||||
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
|
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||||||
|
|
||||||
|
|
@ -15,21 +13,11 @@ export class StrapiNoteRepository implements NoteRepository {
|
||||||
return this.instance;
|
return this.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {}
|
||||||
currentNoteId.subscribe((value) => (this._currentNoteId = value));
|
|
||||||
}
|
|
||||||
|
|
||||||
private _currentNoteId: unknown;
|
private currentNoteId: number | undefined;
|
||||||
private static apiNoteEndpoint: string = "http://localhost:1337/api/notes"
|
private static apiNoteEndpoint: string = "http://localhost:1337/api/notes"
|
||||||
|
|
||||||
public set currentNoteId(value: number | undefined) {
|
|
||||||
currentNoteId.set(value || -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get currentNoteId(): number {
|
|
||||||
return <number>this._currentNoteId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getNotes(): Promise<Note[]>{
|
public async getNotes(): Promise<Note[]>{
|
||||||
const response = await StrapiNoteRepository.fetchStrapiNoteEndpoint("/", 'GET');
|
const response = await StrapiNoteRepository.fetchStrapiNoteEndpoint("/", 'GET');
|
||||||
return await response.json();
|
return await response.json();
|
||||||
|
|
@ -41,7 +29,7 @@ export class StrapiNoteRepository implements NoteRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getCurrentNote(): Promise<Note | void> {
|
public async getCurrentNote(): Promise<Note | void> {
|
||||||
if (this._currentNoteId === null || this._currentNoteId === undefined) {
|
if (this.currentNoteId === null || this.currentNoteId === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return await this.getNote(this.currentNoteId);
|
return await this.getNote(this.currentNoteId);
|
||||||
|
|
@ -65,7 +53,7 @@ export class StrapiNoteRepository implements NoteRepository {
|
||||||
let requestInit: RequestInit = {
|
let requestInit: RequestInit = {
|
||||||
method: method,
|
method: method,
|
||||||
headers: {
|
headers: {
|
||||||
authorization: StrapiNoteRepository.getAuthorizationHeader()
|
authorization: StrapiNoteRepository.mockedGetAuthorizationHeader()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (body) {
|
if (body) {
|
||||||
|
|
@ -78,9 +66,8 @@ export class StrapiNoteRepository implements NoteRepository {
|
||||||
return "bearer TOKEN"
|
return "bearer TOKEN"
|
||||||
}
|
}
|
||||||
|
|
||||||
static getAuthorizationHeader() {
|
private static getAuthorizationHeader() {
|
||||||
// @ts-ignore
|
const jwt = parseCookies().jwt;
|
||||||
const jwt = parseCookies('/').jwt;
|
|
||||||
return `bearer ${jwt}`
|
return `bearer ${jwt}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
import type {UserRepository} from "./UserRepository";
|
|
||||||
import type {Authentication} from "../../authentication";
|
|
||||||
import type {HttpMethod} from "@sveltejs/kit/types/private";
|
|
||||||
import {StrapiNoteRepository} from "../note/StrapiNoteRepository";
|
|
||||||
import {error} from "@sveltejs/kit";
|
|
||||||
import {User} from "../../user";
|
|
||||||
|
|
||||||
export class StrapiUserRepo implements UserRepository {
|
|
||||||
private static instance: StrapiUserRepo;
|
|
||||||
|
|
||||||
public static getInstance(verification: boolean = true): StrapiUserRepo {
|
|
||||||
if (this.instance === undefined || this.instance === null) {
|
|
||||||
this.instance = new StrapiUserRepo();
|
|
||||||
this.instance.verify().then(() => {
|
|
||||||
if (verification && !this.instance.verified) {
|
|
||||||
window.location.href = "/login";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private verified: boolean = false;
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private static api: string = "http://localhost:1337/api"
|
|
||||||
|
|
||||||
private static apiUserEndpoint: string = StrapiUserRepo.api + "/auth/local"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies the current users jwt.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private async verify() {
|
|
||||||
this.verified = false;
|
|
||||||
let result = await this.getMe();
|
|
||||||
if (!result.error) {
|
|
||||||
this.verified = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getMe(): Promise<Authentication> {
|
|
||||||
const response = await StrapiUserRepo.fetchStrapi("/me", "GET", null, true, "/users")
|
|
||||||
return await response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
async registerUser(email: string, username: string, password: string): Promise<Authentication> {
|
|
||||||
const payload = {
|
|
||||||
email: email,
|
|
||||||
password: password,
|
|
||||||
username: username
|
|
||||||
};
|
|
||||||
const response = await StrapiUserRepo.fetchStrapi("/register", "POST", payload, false);
|
|
||||||
return await response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
async loginUser(identifier: string, password: string): Promise<Authentication> {
|
|
||||||
const payload = {
|
|
||||||
identifier: identifier,
|
|
||||||
password: password
|
|
||||||
};
|
|
||||||
const response = await StrapiUserRepo.fetchStrapi("/", "POST", payload, false);
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async fetchStrapi(path: string, method: HttpMethod, body: any | null = null, authorization: boolean = true, customPath: any = null): Promise<Response> {
|
|
||||||
let requestInit: RequestInit = {
|
|
||||||
method: method,
|
|
||||||
};
|
|
||||||
if (authorization && body) {
|
|
||||||
requestInit["headers"] = {
|
|
||||||
authorization: StrapiNoteRepository.getAuthorizationHeader() ?? '',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
} else if (authorization) {
|
|
||||||
requestInit["headers"] = {
|
|
||||||
authorization: StrapiNoteRepository.getAuthorizationHeader() ?? '',
|
|
||||||
}
|
|
||||||
} else if (body) {
|
|
||||||
requestInit["headers"] = {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (body) {
|
|
||||||
requestInit["body"] = JSON.stringify(body)
|
|
||||||
}
|
|
||||||
return await fetch((customPath) ? (this.api + customPath + path) : StrapiUserRepo.apiUserEndpoint + path, requestInit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import type {Authentication} from "../../authentication";
|
|
||||||
|
|
||||||
export interface UserRepository {
|
|
||||||
/**
|
|
||||||
* Registers a new user.
|
|
||||||
* @param email
|
|
||||||
* @param username
|
|
||||||
* @param password
|
|
||||||
*/
|
|
||||||
registerUser(email: string, username: string, password: string): Promise<Authentication>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current user.
|
|
||||||
* @param jwt
|
|
||||||
*/
|
|
||||||
getMe(jwt: string): Promise<Authentication>;
|
|
||||||
|
|
||||||
loginUser(identifier: string, password: string): Promise<Authentication>;
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
|
@ -1,48 +1,87 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {Note} from "../models/types";
|
import type {Note} from "../models/types";
|
||||||
import {onMount} from "svelte";
|
import {onMount} from "svelte";
|
||||||
import {StrapiNoteRepository} from "../models/repos/note/StrapiNoteRepository";
|
import {bearerFetch, jwt} from "../models/PomeloUtils";
|
||||||
import {StrapiUserRepo} from "../models/repos/user/StrapiUserRepo";
|
|
||||||
import {Content, Modal, Trigger} from "sv-popup";
|
const endpoint = "/notes";
|
||||||
|
|
||||||
|
//
|
||||||
|
// //:TODO TEMP!!!
|
||||||
|
// const jsonStr = "[{\"id\":0,\"attributes\":{\"title\":\"mike\",\"content\":\"C Moasta\",\"lastViewed\":\"2022-09-27\"}},{\"id\":1,\"attributes\":{\"title\":\"samc\",\"content\":\"drupal gott\",\"lastViewed\":\"1999-09-09\"}},{\"id\":2,\"attributes\":{\"title\":\"DIO\",\"content\":\"in all CAPS\",\"lastViewed\":\"2022-09-27\"}},{\"id\":3,\"attributes\":{\"title\":\"Eren\",\"content\":\"Jäger\",\"lastViewed\":\"2022-09-27\"}},{\"id\":4,\"attributes\":{\"title\":\"stow\",\"content\":\"Beitn Chef\",\"lastViewed\":\"2022-09-27\"}},{\"id\":5,\"attributes\":{\"title\":\"Wonder of U\",\"content\":\"Umm... so, personally... this is the first time this has happened, so I'm a bit surprised. Only a centimeter away... I mean, I don't think there's ever been someone who's gotten that close to me... without a, you know... calamity occurring. I'm not really... not really sure what happens at one centimeter away... 'cause it's my first time. I don't really understand it either. Seriously. But in the flow of calamity... there's nobody who can attack me. Not a single person. That, I know for sure. Wonder of U.\",\"lastViewed\":\"2022-09-27\"}}]";
|
||||||
|
// //:TODO TEMP!!!
|
||||||
|
//
|
||||||
|
// let notes: Note[] = JSON.parse(jsonStr);
|
||||||
|
|
||||||
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
||||||
const noteRepo: StrapiNoteRepository = StrapiNoteRepository.getInstance();
|
|
||||||
let notes: Note[];
|
let notes: Note[];
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
StrapiUserRepo.getInstance();
|
const response = await bearerFetch(endpoint, jwt);
|
||||||
notes = await noteRepo.getNotes();
|
let data = await response.json();
|
||||||
|
notes = data.data;
|
||||||
notes.forEach(note => {
|
notes.forEach(note => {
|
||||||
note.lastViewed = new Date(note.lastViewed);
|
note.attributes.lastViewed = new Date(note.attributes.lastViewed);
|
||||||
});
|
});
|
||||||
|
console.log(notes);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a Note with the title "New Note" and redirects to editor
|
* Reloads the Notes Listing
|
||||||
|
* (by doing something very intelligent)
|
||||||
*/
|
*/
|
||||||
async function addNoteHandler() {
|
function reloadNotesListing() {
|
||||||
const newTitle = "New Note";
|
notes = notes.filter(i => i === i);
|
||||||
const newNote = await addNote(newTitle);
|
|
||||||
noteRepo.currentNoteId = newNote.id;
|
|
||||||
window.location = "/editor";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new note to the Database
|
* Gives the user a prompt to input the new title of the note and creates it if the title is valid
|
||||||
|
*/
|
||||||
|
function addNotePrompt() {
|
||||||
|
let newTitle = prompt('Name of the new Note');
|
||||||
|
console.log(notes)
|
||||||
|
if (newTitle != null && newTitle != '') {
|
||||||
|
addNote(newTitle);
|
||||||
|
console.log(notes)
|
||||||
|
reloadNotesListing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new note to the "notes" Array with:
|
||||||
|
* * the latest id + 1 (or 0 if no notes exist)
|
||||||
|
* * no content
|
||||||
|
* * the current date as the "lastViewed" property
|
||||||
* @param title The title of the new Note
|
* @param title The title of the new Note
|
||||||
* @return The created Note Object
|
|
||||||
*/
|
*/
|
||||||
async function addNote(title: string): Promise<Note> {
|
function addNote(title: string) {
|
||||||
return await noteRepo.createNote({title: title,});
|
const date = new Date();
|
||||||
|
const newNoteId: number = (notes.length == 0) ? 0 : notes[notes.length - 1].id + 1
|
||||||
|
const note: Note = {
|
||||||
|
id: newNoteId,
|
||||||
|
attributes: {
|
||||||
|
title: title,
|
||||||
|
content: "",
|
||||||
|
lastViewed: date
|
||||||
|
}
|
||||||
|
};
|
||||||
|
notes.push(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the note from the "notes" Array and the database
|
* Gives the user a prompt if they are sure to delete this note and deletes it if they confirm
|
||||||
|
* @param note The note to be deleted
|
||||||
|
*/
|
||||||
|
function deleteNotePrompt(note) {
|
||||||
|
const reallyDelete = confirm("Do you really want to delete this Note?");
|
||||||
|
if (reallyDelete) {
|
||||||
|
deleteNote(note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the note from the "notes" Array
|
||||||
* @param note The note to be deleted
|
* @param note The note to be deleted
|
||||||
*/
|
*/
|
||||||
function deleteNote(note) {
|
function deleteNote(note) {
|
||||||
notes = notes.filter(i => i !== note);
|
notes = notes.filter(i => i !== note);
|
||||||
noteRepo.deleteNote(note.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -62,44 +101,32 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the currentNoteId and redirects to the editor
|
* Handles a click on a note list element
|
||||||
* @param note The note the user clicked on
|
* @param note The note the user clicked on
|
||||||
*/
|
*/
|
||||||
function onNoteLiClick(note) {
|
function onNoteLiClick(note) {
|
||||||
noteRepo.currentNoteId = note.id;
|
|
||||||
window.location = "/editor";
|
window.location = "/editor";
|
||||||
|
note.attributes.lastViewed = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the modal (popup for deletion)
|
|
||||||
*/
|
|
||||||
async function closeModal() {
|
|
||||||
closeModalBool = true;
|
|
||||||
await sleep(1);
|
|
||||||
closeModalBool = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let closeModalBool = false;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>PomeloNote | Home</title>
|
<title>PomeloNote | Home</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css"
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" rel="stylesheet">
|
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Add Note Button -->
|
<!-- Add Note Button -->
|
||||||
<div class="offset-md-7 col-md-1">
|
<div class="offset-md-7 col-md-1">
|
||||||
<button class="btn btn-primary" on:click={() => addNoteHandler()}>Add Note</button>
|
<button class="btn btn-primary" on:click={() => addNotePrompt()}>Add Note</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -114,42 +141,18 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-10" on:click={() => onNoteLiClick(note)}>
|
<div class="col-10" on:click={() => onNoteLiClick(note)}>
|
||||||
<div>
|
<div>
|
||||||
{note.title}
|
{note.attributes.title}
|
||||||
</div>
|
</div>
|
||||||
<div class="list-date-text">
|
<div class="list-date-text">
|
||||||
{note.lastViewed.toLocaleDateString()}
|
{note.attributes.lastViewed.toLocaleDateString()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
<!-- Delete Note Popup -->
|
<button style="display: none" id={"noteButton" + note.id}
|
||||||
<Modal basic small={true} button={false} close={closeModalBool}>
|
on:click={() => deleteNotePrompt(note)}>
|
||||||
<Content>
|
<i class="bi bi-x"></i>
|
||||||
<div class="row" style="margin-bottom: 10px;">
|
</button>
|
||||||
<h5>Do you really want to delete the "{note.title}" note?</h5>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4 offset-2">
|
|
||||||
<button class="btn btn-primary"
|
|
||||||
on:click={() => deleteNote(note)}
|
|
||||||
on:click={() => closeModal()}>
|
|
||||||
<b>Yes</b>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="col-4 offset-2">
|
|
||||||
<button class="btn btn-primary" autofocus
|
|
||||||
on:click={() => closeModal()}>
|
|
||||||
<b>No</b>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Content>
|
|
||||||
<Trigger>
|
|
||||||
<button style="display: none" id={"noteButton" + note.id}>
|
|
||||||
<i class="bi bi-x"></i>
|
|
||||||
</button>
|
|
||||||
</Trigger>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -163,11 +166,17 @@
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@import "../customBootstrap.css";
|
|
||||||
|
|
||||||
html,
|
html,
|
||||||
:root {
|
:root {
|
||||||
|
--main-txt-color: black;
|
||||||
--sub-txt-color: gray;
|
--sub-txt-color: gray;
|
||||||
|
--cross-txt-color: red;
|
||||||
|
|
||||||
|
--color-primary: #fff494;
|
||||||
|
--color-primary-600: #fff17a;
|
||||||
|
--color-primary-700: #ffec47;
|
||||||
|
--color-primary-800: #ffe714;
|
||||||
|
--color-primary-900: #e0c900;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|
@ -221,6 +230,18 @@
|
||||||
color: var(--cross-txt-color);
|
color: var(--cross-txt-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: var(--color-primary-800);
|
||||||
|
border: var(--color-primary-800);
|
||||||
|
color: var(--main-txt-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: var(--color-primary-900);
|
||||||
|
border: var(--color-primary-900);
|
||||||
|
color: var(--main-txt-color);
|
||||||
|
}
|
||||||
|
|
||||||
.list-date-text {
|
.list-date-text {
|
||||||
color: var(--sub-txt-color);
|
color: var(--sub-txt-color);
|
||||||
font-size: 0.8314159265358979323846264338rem;
|
font-size: 0.8314159265358979323846264338rem;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,20 @@
|
||||||
import {StrapiUserRepo} from "../models/repos/user/StrapiUserRepo";
|
import {bearerFetch, jwt} from "../models/PomeloUtils";
|
||||||
|
|
||||||
/** @type {import('./$types').PageLoad} */
|
/** @type {import('./$types').PageLoad} */
|
||||||
export async function load() {
|
export async function load() {
|
||||||
// StrapiUserRepo.getInstance();
|
let invalid = !jwt;
|
||||||
|
|
||||||
|
if (!invalid) {
|
||||||
|
const request = await bearerFetch("/users/me", jwt);
|
||||||
|
const response = await request.json();
|
||||||
|
|
||||||
|
invalid = "error" in response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalid) {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
// @ts-ignore
|
||||||
|
window.location = "/login";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,81 +1,24 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {Note} from "../../models/types";
|
import type {Note} from "../../models/types";
|
||||||
import {StrapiNoteRepository} from "../../models/repos/note/StrapiNoteRepository";
|
|
||||||
import {onMount} from "svelte";
|
|
||||||
|
|
||||||
let noteRepo: StrapiNoteRepository;
|
let notes: Note[] = JSON.parse(window.localStorage.getItem("notes"));
|
||||||
let currentNote: Note;
|
const clickedNoteId = window.localStorage.getItem("clickedNoteId");
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
noteRepo = StrapiNoteRepository.getInstance();
|
|
||||||
try {
|
|
||||||
currentNote = await noteRepo.getNote(noteRepo.currentNoteId);
|
|
||||||
} catch {
|
|
||||||
returnToListing();
|
|
||||||
}
|
|
||||||
title = (<Note>currentNote).title;
|
|
||||||
content = (<Note>currentNote).content;
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* saves the currently opened Note and returns to listing
|
|
||||||
*/
|
|
||||||
function saveAndQuit() {
|
|
||||||
noteRepo.updateNote(currentNote.id, {
|
|
||||||
"title": title,
|
|
||||||
"content": content,
|
|
||||||
})
|
|
||||||
returnToListing();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* redirects to listing
|
|
||||||
*/
|
|
||||||
function returnToListing() {
|
|
||||||
window.location = "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
export let title: string, content: string;
|
|
||||||
|
|
||||||
|
const currNote = notes.find((note)=>{
|
||||||
|
return note.id === parseInt(clickedNoteId);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Editor</title>
|
<title>{"Pomelonote | Edit " + currNote.title}</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet"
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
|
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<div class="offset-3 col-6 wrapper">
|
<div class="offset-3 col-6">
|
||||||
<h1 class="">{title === "" ? "" : title}</h1>
|
{currNote.content}
|
||||||
<input bind:value={title} class="input"> <br/>
|
|
||||||
<textarea bind:value={content} class="input textarea"></textarea>
|
|
||||||
<div class="button-container">
|
|
||||||
<button on:click={() => saveAndQuit()} class="btn btn-primary">Save</button>
|
|
||||||
<button on:click={() => returnToListing()} class="btn btn-outline-danger">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
<style>
|
|
||||||
@import "../../customBootstrap.css";
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-container {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textarea {
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {setCookie} from "nookies";
|
import {setCookie} from "nookies";
|
||||||
import {SvelteToast} from '@zerodevx/svelte-toast'
|
import type {Authentication} from "./models/authentication";
|
||||||
|
import { SvelteToast } from '@zerodevx/svelte-toast'
|
||||||
|
import {createErrorToast} from "../../models/customToasts";
|
||||||
import logo from "../../resources/images/logo2.svg";
|
import logo from "../../resources/images/logo2.svg";
|
||||||
import {handleErrorsFromResponseWithToast} from "../../models/PomeloUtils";
|
|
||||||
import {StrapiUserRepo} from "../../models/repos/user/StrapiUserRepo";
|
|
||||||
|
|
||||||
let user: string;
|
let user: string;
|
||||||
let password: string;
|
let password: string;
|
||||||
|
|
@ -14,12 +14,31 @@
|
||||||
* Handles the button click.
|
* Handles the button click.
|
||||||
*/
|
*/
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
const userRepo: StrapiUserRepo = StrapiUserRepo.getInstance(false);
|
const endpoint = "http://localhost:1337/api/auth/local";
|
||||||
|
const payload = {
|
||||||
|
identifier: user,
|
||||||
|
password: password
|
||||||
|
};
|
||||||
|
|
||||||
const response = await userRepo.loginUser(user, password);
|
const login = await fetch(endpoint, {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
})
|
||||||
|
|
||||||
if (response.error != null) {
|
const response: Authentication = await login.json();
|
||||||
handleErrorsFromResponseWithToast(response);
|
|
||||||
|
if (response.error != null){
|
||||||
|
if (response.error.details.errors){
|
||||||
|
for (const error of response.error.details.errors) {
|
||||||
|
createErrorToast(error.message);
|
||||||
|
}
|
||||||
|
} else{
|
||||||
|
createErrorToast(response.error.message);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (rememberMe) {
|
if (rememberMe) {
|
||||||
setCookie(null, 'jwt', response.jwt, {
|
setCookie(null, 'jwt', response.jwt, {
|
||||||
|
|
@ -37,34 +56,34 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>PomeloNote | Login</title>
|
<title>PomeloNote | Login</title>
|
||||||
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css"
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" rel="stylesheet">
|
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<main class="form-signin w-100 m-auto">
|
<main class="form-signin w-100 m-auto">
|
||||||
|
|
||||||
<img alt="Logo" class="img-fluid" src="{logo}">
|
<img class="img-fluid" src="{logo}" alt="Logo">
|
||||||
<h1 class="h3 mb-3 fw-normal">Please sign in</h1>
|
<h1 class="h3 mb-3 fw-normal">Please sign in</h1>
|
||||||
|
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input bind:value={user} class="form-control" id="floatingInput" placeholder="name@example.com" type="text">
|
<input type="email" class="form-control" id="floatingInput" placeholder="name@example.com" bind:value={user}>
|
||||||
<label for="floatingInput">Email address or username</label>
|
<label for="floatingInput">Email address or username</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input bind:value={password} class="form-control" id="floatingPassword" placeholder="Password" type="password">
|
<input type="password" class="form-control" id="floatingPassword" placeholder="Password" bind:value={password}>
|
||||||
<label for="floatingPassword">Password</label>
|
<label for="floatingPassword">Password</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="checkbox mb-3">
|
<div class="checkbox mb-3">
|
||||||
<label>
|
<label>
|
||||||
<input bind:checked={rememberMe} type="checkbox" value="rememberMe"> Remember me
|
<input type="checkbox" value="rememberMe" bind:checked={rememberMe}> Remember me
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<button class="w-100 btn btn-lg btn-primary" on:click={handleSubmit}>Sign in</button>
|
<button class="w-100 btn btn-lg btn-primary" on:click={handleSubmit}>Sign in</button>
|
||||||
<a class="opacity-75 d-flex justify-content-center text-center fs-6" href="/register">No user yet? Register.</a>
|
<a href="/register" class="opacity-75 d-flex justify-content-center text-center fs-6">No user yet? Register.</a>
|
||||||
<p class="mt-5 mb-3 text-muted">©2022</p>
|
<p class="mt-5 mb-3 text-muted">©2022</p>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
@ -72,7 +91,42 @@
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@import "../../userInput.css";
|
html,
|
||||||
@import "../../customBootstrap.css";
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 40px;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin {
|
||||||
|
max-width: 330px;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin .form-floating:focus-within {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin input[type="email"] {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin input[type="password"] {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-fluid{
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type {User} from "./user";
|
import type {User} from "../../../models/user";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User Login Auth.
|
* User Login Auth.
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import logo from "../../resources/images/logo2.svg";
|
import logo from "../../resources/images/logo2.svg";
|
||||||
import {SvelteToast} from "@zerodevx/svelte-toast";
|
import {SvelteToast} from "@zerodevx/svelte-toast";
|
||||||
import {StrapiUserRepo} from "../../models/repos/user/StrapiUserRepo";
|
import type {Authentication} from "../login/models/authentication";
|
||||||
import {handleErrorsFromResponseWithToast} from "../../models/PomeloUtils";
|
import {createErrorToast} from "../../models/customToasts";
|
||||||
|
|
||||||
let user: string;
|
let user: string;
|
||||||
let password: string;
|
let password: string;
|
||||||
|
|
@ -13,12 +13,32 @@
|
||||||
* Handles the button click.
|
* Handles the button click.
|
||||||
*/
|
*/
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
const userRepo: StrapiUserRepo = StrapiUserRepo.getInstance(false);
|
const endpoint = "http://localhost:1337/api/auth/local/register";
|
||||||
|
const payload = {
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
username: user
|
||||||
|
};
|
||||||
|
|
||||||
const response = await userRepo.registerUser(email, user, password);
|
const login = await fetch(endpoint, {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
})
|
||||||
|
|
||||||
|
const response: Authentication = await login.json();
|
||||||
|
|
||||||
if (response.error != null) {
|
if (response.error != null) {
|
||||||
handleErrorsFromResponseWithToast(response);
|
if (response.error.details.errors) {
|
||||||
|
for (const error of response.error.details.errors) {
|
||||||
|
createErrorToast(error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
createErrorToast(response.error.message);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
window.location = "/login";
|
window.location = "/login";
|
||||||
}
|
}
|
||||||
|
|
@ -29,29 +49,29 @@
|
||||||
lang="en">
|
lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>PomeloNote | Register</title>
|
<title>PomeloNote | Register</title>
|
||||||
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css"
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" rel="stylesheet">
|
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<main class="form-signin w-100 m-auto">
|
<main class="form-signin w-100 m-auto">
|
||||||
|
|
||||||
<img alt="Logo" class="img-fluid" src="{logo}">
|
<img class="img-fluid" src="{logo}" alt="Logo">
|
||||||
<h1 class="h3 mb-3 fw-normal">Register a new user</h1>
|
<h1 class="h3 mb-3 fw-normal">Register a new user</h1>
|
||||||
|
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input bind:value={user} class="form-control" id="floatingUsr" placeholder="exampleUsername" type="text">
|
<input type="text" class="form-control" id="floatingUsr" placeholder="exampleUsername" bind:value={user}>
|
||||||
<label for="floatingUsr">Username</label>
|
<label for="floatingUsr">Username</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input bind:value={email} class="form-control" id="floatingInput" placeholder="name@example.com" type="email">
|
<input type="email" class="form-control" id="floatingInput" placeholder="name@example.com" bind:value={email}>
|
||||||
<label for="floatingInput">Email address</label>
|
<label for="floatingInput">Email address</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input bind:value={password} class="form-control" id="floatingPassword" placeholder="Password" type="password">
|
<input type="password" class="form-control" id="floatingPassword" placeholder="Password" bind:value={password}>
|
||||||
<label for="floatingPassword">Password</label>
|
<label for="floatingPassword">Password</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -59,7 +79,7 @@
|
||||||
Register user
|
Register user
|
||||||
{#if user}: {user} {/if}
|
{#if user}: {user} {/if}
|
||||||
</button>
|
</button>
|
||||||
<a class="opacity-75 d-flex justify-content-center text-center fs-6" href="/login">Already registered? Login.</a>
|
<a href="/login" class="opacity-75 d-flex justify-content-center text-center fs-6">Already registered? Login.</a>
|
||||||
<p class="mt-5 mb-3 text-muted">©2022</p>
|
<p class="mt-5 mb-3 text-muted">©2022</p>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
@ -68,6 +88,46 @@
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@import "../../userInput.css";
|
html,
|
||||||
@import "../../customBootstrap.css";
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 40px;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin {
|
||||||
|
max-width: 330px;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin .form-floating:focus-within {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin input[type="text"] {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin input[type="email"] {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.form-signin input[type="password"] {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-fluid {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
/// <reference lib="webworker" />
|
|
||||||
|
|
||||||
import { build, files, version } from '$service-worker';
|
|
||||||
|
|
||||||
const worker = ServiceWorkerGlobalScope;
|
|
||||||
// const FILES = cache + version;
|
|
||||||
|
|
||||||
const to_cache = build.concat(files);
|
|
||||||
const staticAssets = new Set(to_cache);
|
|
||||||
|
|
||||||
worker.addEventListener('install', (event) => {
|
|
||||||
event.waitUntil(
|
|
||||||
caches
|
|
||||||
.open(FILES)
|
|
||||||
.then((cache) => cache.addAll(to_cache))
|
|
||||||
.then(() => {
|
|
||||||
worker.skipWaiting();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
worker.addEventListener('activate', (event) => {
|
|
||||||
event.waitUntil(
|
|
||||||
caches.keys().then(async (keys) => {
|
|
||||||
// delete old caches
|
|
||||||
for (const key of keys) {
|
|
||||||
if (key !== FILES) await caches.delete(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
worker.clients.claim();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch the asset from the network and store it in the cache.
|
|
||||||
* Fall back to the cache if the user is offline.
|
|
||||||
*/
|
|
||||||
async function fetchAndCache(request) {
|
|
||||||
const cache = await caches.open(offline + version);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(request);
|
|
||||||
cache.put(request, response.clone());
|
|
||||||
return response;
|
|
||||||
} catch (err) {
|
|
||||||
const response = await cache.match(request);
|
|
||||||
if (response) return response;
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
worker.addEventListener('fetch', (event) => {
|
|
||||||
if (event.request.method !== 'GET' || event.request.headers.has('range')) return;
|
|
||||||
|
|
||||||
const url = new URL(event.request.url);
|
|
||||||
|
|
||||||
// don't try to handle e.g. data: URIs
|
|
||||||
const isHttp = url.protocol.startsWith('http');
|
|
||||||
const isDevServerRequest =
|
|
||||||
url.hostname === self.location.hostname && url.port !== self.location.port;
|
|
||||||
const isStaticAsset = url.host === self.location.host && staticAssets.has(url.pathname);
|
|
||||||
const skipBecauseUncached = event.request.cache === 'only-if-cached' && !isStaticAsset;
|
|
||||||
|
|
||||||
if (isHttp && !isDevServerRequest && !skipBecauseUncached) {
|
|
||||||
event.respondWith(
|
|
||||||
(async () => {
|
|
||||||
// always serve static files and bundler-generated assets from cache.
|
|
||||||
// if your application has other URLs with data that will never change,
|
|
||||||
// set this variable to true for them and they will only be fetched once.
|
|
||||||
const cachedAsset = isStaticAsset && (await caches.match(event.request));
|
|
||||||
|
|
||||||
return cachedAsset || fetchAndCache(event.request);
|
|
||||||
})()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import {writable} from "svelte/store";
|
|
||||||
import {browser} from "$app/environment"
|
|
||||||
export const currentNoteId = writable<number>();
|
|
||||||
if (browser) {
|
|
||||||
currentNoteId.set(Number(localStorage.getItem("currentNoteId") || ""))
|
|
||||||
currentNoteId.subscribe(val => localStorage.setItem("currentNoteId", String(val)));
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
html,
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
align-items: center;
|
|
||||||
padding-top: 40px;
|
|
||||||
padding-bottom: 40px;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-signin {
|
|
||||||
max-width: 330px;
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-signin .form-floating:focus-within {
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-signin input[type="text"] {
|
|
||||||
margin-bottom: -1px;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-signin input[type="email"] {
|
|
||||||
margin-bottom: -1px;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-signin input[type="password"] {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img-fluid {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"lang": "en",
|
|
||||||
"dir": "/",
|
|
||||||
"name": "Pomelo Note",
|
|
||||||
"short_name": "Pomelo",
|
|
||||||
"description": "Best Note App",
|
|
||||||
"theme_color": "#000",
|
|
||||||
"background_color": "#000",
|
|
||||||
"display": "standalone",
|
|
||||||
"orientation": "portrait",
|
|
||||||
"prefer_related_applications": false,
|
|
||||||
"scope": "/",
|
|
||||||
"start_url": "/",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "../resources/icons/android-icon-36x36.png",
|
|
||||||
"sizes": "36x36",
|
|
||||||
"type": "image\/png",
|
|
||||||
"density": "0.75"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "../resources/icons/android-icon-48x48.png",
|
|
||||||
"sizes": "48x48",
|
|
||||||
"type": "image\/png",
|
|
||||||
"density": "1.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "../resources/icons/android-icon-72x72.png",
|
|
||||||
"sizes": "72x72",
|
|
||||||
"type": "image\/png",
|
|
||||||
"density": "1.5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "../resources/icons/android-icon-96x96.png",
|
|
||||||
"sizes": "96x96",
|
|
||||||
"type": "image\/png",
|
|
||||||
"density": "2.0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"splash_pages": null
|
|
||||||
}
|
|
||||||
|
|
@ -2,14 +2,7 @@ import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import type { UserConfig } from 'vite';
|
import type { UserConfig } from 'vite';
|
||||||
|
|
||||||
const config: UserConfig = {
|
const config: UserConfig = {
|
||||||
plugins: [sveltekit()],
|
plugins: [sveltekit()]
|
||||||
|
|
||||||
server: {
|
|
||||||
fs: {
|
|
||||||
// Allow serving files from one level up to the project root
|
|
||||||
allow: ['..'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|
|
||||||