Compare commits
74 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
809e7ce6d7 | ||
|
|
576e65dca6 | ||
|
|
e0505755da | ||
|
|
4fe86d33b6 | ||
|
|
3cd93ac7da | ||
|
|
7c4ed1d2b2 | ||
|
|
67395c9ebf | ||
|
|
3ef834e103 | ||
|
|
cf9da4c6a0 | ||
|
|
58f2a53539 | ||
|
|
46e891d429 | ||
|
|
01d9377238 | ||
|
|
7819b461b8 | ||
|
|
f0e2c9ca94 | ||
|
|
ad94658aef | ||
|
|
1b52eaac46 | ||
|
|
e03b58057e | ||
|
|
1ffd08082b | ||
|
|
942ee3805d | ||
|
|
75034e0602 | ||
|
|
5bd335dbd6 | ||
|
|
ffce155154 | ||
|
|
245de43c78 | ||
|
|
bb9ee26bd2 | ||
|
|
158d49d221 | ||
|
|
fcd2f16401 | ||
|
|
f1331157fd | ||
|
|
f021209531 | ||
|
|
d5867c4f0d | ||
|
|
b8c3603cbb | ||
|
|
dbde003fc2 | ||
|
|
239c73fb8e | ||
|
|
9589a21f96 | ||
|
|
c1c6804a63 | ||
|
|
08166dda95 | ||
|
|
2e3af63c72 | ||
|
|
d438bc4b14 | ||
|
|
00f74d4228 | ||
|
|
2d06680bab | ||
|
|
39d8a2034f | ||
|
|
8d63c8fd4e | ||
|
|
63e37061de | ||
|
|
319828b8b1 | ||
|
|
952917d415 | ||
|
|
c85fbe915f | ||
|
|
4091f6c8f5 | ||
|
|
e9c21409cd | ||
|
|
7068ae402e | ||
|
|
a9bebd789e | ||
|
|
fa94a99567 | ||
|
|
180a26344a | ||
|
|
f7686371c5 | ||
|
|
51735f79dc | ||
|
|
53854b23cd | ||
|
|
fa91d5f670 | ||
|
|
ad23d05000 | ||
|
|
154d496208 | ||
|
|
36d373cdc3 | ||
|
|
f266f3579c | ||
|
|
6fd7726ae0 | ||
|
|
bee5b04441 | ||
|
|
cdefc2e993 | ||
|
|
7abdbe605b | ||
|
|
2d193969f7 | ||
|
|
22396d1a00 | ||
|
|
031a4e5259 | ||
|
|
03b1695d9a | ||
|
|
03e75ffe2a | ||
|
|
93f9979259 | ||
|
|
4125b433e5 | ||
|
|
d66b9272b7 | ||
|
|
07e014cd8f | ||
|
|
7a5ba150f7 | ||
|
|
2eeda6fc5c |
|
|
@ -1,4 +1,13 @@
|
|||
# 🚀 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:
|
||||
``docker-compose up --build -d``
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ module.exports = createCoreController(noteUid, ({strapi}) => ({
|
|||
lastViewed: Date.now()
|
||||
}
|
||||
})
|
||||
entry = await strapi.entityService.findOne(noteUid, noteId, {
|
||||
populate: ['owners'],
|
||||
});
|
||||
return JSON.stringify(entry);
|
||||
} else {
|
||||
ctx.response.status = 403;
|
||||
|
|
@ -63,22 +66,26 @@ module.exports = createCoreController(noteUid, ({strapi}) => ({
|
|||
async update(ctx) {
|
||||
const noteId = getNoteIdFromUrl(ctx.request.url)
|
||||
const userId = ctx.state.user.id;
|
||||
const requestBody = ctx.request.body;
|
||||
const requestBody = JSON.parse(ctx.request.body);
|
||||
console.log(JSON.stringify(requestBody, null, 2))
|
||||
const entry = await strapi.entityService.findOne(noteUid, noteId, {
|
||||
populate: ['owners'],
|
||||
});
|
||||
const authorized = entry.owners.some(owner => owner.id === userId)
|
||||
let allPreviousOwnersKept = false;
|
||||
let allPreviousOwnersKept = true;
|
||||
if (requestBody.data.hasOwnProperty("owners")) {
|
||||
allPreviousOwnersKept = entry.owners.every(owner => requestBody.data.owners.includes(owner));
|
||||
}
|
||||
console.log({
|
||||
"auth": authorized,
|
||||
"allprev": allPreviousOwnersKept,
|
||||
})
|
||||
if (!authorized) {
|
||||
ctx.response.status = 403;
|
||||
} else if (!allPreviousOwnersKept) {
|
||||
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
|
||||
|
|
@ -87,7 +94,8 @@ module.exports = createCoreController(noteUid, ({strapi}) => ({
|
|||
*/
|
||||
async create(ctx) {
|
||||
const userId = ctx.state.user.id;
|
||||
const requestBody = ctx.request.body;
|
||||
const requestBody = JSON.parse(ctx.request.body);
|
||||
console.log(requestBody);
|
||||
const response = await strapi.entityService.create(noteUid, {
|
||||
data: {
|
||||
title: requestBody.data.title,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
3
docs/_config.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
remote_theme: pages-themes/leap-day@v0.2.0
|
||||
plugins:
|
||||
- jekyll-remote-theme
|
||||
4
docs/_data/devs.csv
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
name,github,image
|
||||
Jonas Weissengruber,j-weissen,jowei
|
||||
Stefan Prechtler,s-prechtl,stef
|
||||
David Hain,d-hain,dave
|
||||
|
BIN
docs/images/dave.jpg
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
docs/images/delete.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/images/editor.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
docs/images/jowei.jpg
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
docs/images/listing.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/images/login.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
docs/images/register.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
docs/images/stef.jpg
Normal file
|
After Width: | Height: | Size: 134 KiB |
31
docs/index.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# 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,7 +9,9 @@
|
|||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"bootstrap-icons": "^1.9.1",
|
||||
"nookies": "^2.5.2"
|
||||
"nookies": "^2.5.2",
|
||||
"sv-popup": "^0.2.5",
|
||||
"webworker": "^0.8.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "next",
|
||||
|
|
@ -2083,6 +2085,11 @@
|
|||
"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": {
|
||||
"version": "3.50.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.50.1.tgz",
|
||||
|
|
@ -2358,6 +2365,14 @@
|
|||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"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": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
|
|
@ -3824,6 +3839,11 @@
|
|||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"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": {
|
||||
"version": "3.50.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.50.1.tgz",
|
||||
|
|
@ -3981,6 +4001,11 @@
|
|||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"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": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@
|
|||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"bootstrap-icons": "^1.9.1",
|
||||
"nookies": "^2.5.2",
|
||||
"bootstrap-icons": "^1.9.1"
|
||||
"sv-popup": "^0.2.5",
|
||||
"webworker": "^0.8.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-theme="emerald">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="../src/resources/images/logo2.svg" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="manifest" href="../static/manifest.json">
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
29
frontend/svelte/src/customBootstrap.css
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
|
||||
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,5 @@
|
|||
import {parseCookies} from "nookies";
|
||||
import type {Authentication} from "./authentication";
|
||||
import {createErrorToast} from "./customToasts";
|
||||
|
||||
/**
|
||||
* Capitalises first letter of string.
|
||||
|
|
@ -22,13 +23,14 @@ export async function bearerFetch(endpoint: string, jwt: string, baseUrl: string
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
const getJwtCookie = () => {
|
||||
// @ts-ignore
|
||||
return parseCookies("/").jwt;
|
||||
};
|
||||
|
||||
/**
|
||||
* JWT Cookie
|
||||
*/
|
||||
export const jwt: string = getJwtCookie();
|
||||
export function handleErrorsFromResponseWithToast(response: Authentication) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type {User} from "../../../models/user";
|
||||
import type {User} from "./user";
|
||||
|
||||
/**
|
||||
* User Login Auth.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type {Note} from "./types";
|
||||
import type {Note} from "../../types";
|
||||
|
||||
export interface NoteRepository {
|
||||
getNotes(): Promise<Note[]>;
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import type {Note} from "./types";
|
||||
import type {Note} from "../../types";
|
||||
import {parseCookies} from "nookies";
|
||||
import type {NoteRepository} from "./NoteRepository";
|
||||
import {currentNoteId} from "../../../stores";
|
||||
|
||||
|
||||
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||||
|
||||
|
|
@ -13,11 +15,21 @@ export class StrapiNoteRepository implements NoteRepository {
|
|||
return this.instance;
|
||||
}
|
||||
|
||||
private constructor() {}
|
||||
private constructor() {
|
||||
currentNoteId.subscribe((value) => (this._currentNoteId = value));
|
||||
}
|
||||
|
||||
private currentNoteId: number | undefined;
|
||||
private _currentNoteId: unknown;
|
||||
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[]>{
|
||||
const response = await StrapiNoteRepository.fetchStrapiNoteEndpoint("/", 'GET');
|
||||
return await response.json();
|
||||
|
|
@ -29,7 +41,7 @@ export class StrapiNoteRepository implements NoteRepository {
|
|||
}
|
||||
|
||||
public async getCurrentNote(): Promise<Note | void> {
|
||||
if (this.currentNoteId === null || this.currentNoteId === undefined) {
|
||||
if (this._currentNoteId === null || this._currentNoteId === undefined) {
|
||||
return;
|
||||
}
|
||||
return await this.getNote(this.currentNoteId);
|
||||
|
|
@ -53,7 +65,7 @@ export class StrapiNoteRepository implements NoteRepository {
|
|||
let requestInit: RequestInit = {
|
||||
method: method,
|
||||
headers: {
|
||||
authorization: StrapiNoteRepository.mockedGetAuthorizationHeader()
|
||||
authorization: StrapiNoteRepository.getAuthorizationHeader()
|
||||
}
|
||||
};
|
||||
if (body) {
|
||||
|
|
@ -66,8 +78,9 @@ export class StrapiNoteRepository implements NoteRepository {
|
|||
return "bearer TOKEN"
|
||||
}
|
||||
|
||||
private static getAuthorizationHeader() {
|
||||
const jwt = parseCookies().jwt;
|
||||
static getAuthorizationHeader() {
|
||||
// @ts-ignore
|
||||
const jwt = parseCookies('/').jwt;
|
||||
return `bearer ${jwt}`
|
||||
}
|
||||
}
|
||||
93
frontend/svelte/src/models/repos/user/StrapiUserRepo.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
19
frontend/svelte/src/models/repos/user/UserRepository.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
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>;
|
||||
}
|
||||
BIN
frontend/svelte/src/resources/icons/android-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
frontend/svelte/src/resources/icons/android-icon-192x192.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
frontend/svelte/src/resources/icons/android-icon-36x36.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
frontend/svelte/src/resources/icons/android-icon-48x48.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
frontend/svelte/src/resources/icons/android-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
frontend/svelte/src/resources/icons/android-icon-96x96.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -1,87 +1,48 @@
|
|||
<script lang="ts">
|
||||
import type {Note} from "../models/types";
|
||||
import {onMount} from "svelte";
|
||||
import {bearerFetch, jwt} from "../models/PomeloUtils";
|
||||
|
||||
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);
|
||||
import {StrapiNoteRepository} from "../models/repos/note/StrapiNoteRepository";
|
||||
import {StrapiUserRepo} from "../models/repos/user/StrapiUserRepo";
|
||||
import {Content, Modal, Trigger} from "sv-popup";
|
||||
|
||||
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
||||
const noteRepo: StrapiNoteRepository = StrapiNoteRepository.getInstance();
|
||||
let notes: Note[];
|
||||
|
||||
onMount(async () => {
|
||||
const response = await bearerFetch(endpoint, jwt);
|
||||
let data = await response.json();
|
||||
notes = data.data;
|
||||
StrapiUserRepo.getInstance();
|
||||
notes = await noteRepo.getNotes();
|
||||
notes.forEach(note => {
|
||||
note.attributes.lastViewed = new Date(note.attributes.lastViewed);
|
||||
note.lastViewed = new Date(note.lastViewed);
|
||||
});
|
||||
console.log(notes);
|
||||
});
|
||||
|
||||
/**
|
||||
* Reloads the Notes Listing
|
||||
* (by doing something very intelligent)
|
||||
* Adds a Note with the title "New Note" and redirects to editor
|
||||
*/
|
||||
function reloadNotesListing() {
|
||||
notes = notes.filter(i => i === i);
|
||||
async function addNoteHandler() {
|
||||
const newTitle = "New Note";
|
||||
const newNote = await addNote(newTitle);
|
||||
noteRepo.currentNoteId = newNote.id;
|
||||
window.location = "/editor";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Adds a new note to the Database
|
||||
* @param title The title of the new Note
|
||||
* @return The created Note Object
|
||||
*/
|
||||
function addNote(title: string) {
|
||||
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);
|
||||
async function addNote(title: string): Promise<Note> {
|
||||
return await noteRepo.createNote({title: title,});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Deletes the note from the "notes" Array and the database
|
||||
* @param note The note to be deleted
|
||||
*/
|
||||
function deleteNote(note) {
|
||||
notes = notes.filter(i => i !== note);
|
||||
noteRepo.deleteNote(note.id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -101,32 +62,44 @@
|
|||
}
|
||||
|
||||
/**
|
||||
* Handles a click on a note list element
|
||||
* Sets the currentNoteId and redirects to the editor
|
||||
* @param note The note the user clicked on
|
||||
*/
|
||||
function onNoteLiClick(note) {
|
||||
noteRepo.currentNoteId = note.id;
|
||||
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>
|
||||
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<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@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
|
||||
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css"
|
||||
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<div class="row">
|
||||
<!-- Add Note Button -->
|
||||
<div class="offset-md-7 col-md-1">
|
||||
<button class="btn btn-primary" on:click={() => addNotePrompt()}>Add Note</button>
|
||||
<button class="btn btn-primary" on:click={() => addNoteHandler()}>Add Note</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -141,18 +114,42 @@
|
|||
<div class="row">
|
||||
<div class="col-10" on:click={() => onNoteLiClick(note)}>
|
||||
<div>
|
||||
{note.attributes.title}
|
||||
{note.title}
|
||||
</div>
|
||||
<div class="list-date-text">
|
||||
{note.attributes.lastViewed.toLocaleDateString()}
|
||||
{note.lastViewed.toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-1">
|
||||
<button style="display: none" id={"noteButton" + note.id}
|
||||
on:click={() => deleteNotePrompt(note)}>
|
||||
<!-- Delete Note Popup -->
|
||||
<Modal basic small={true} button={false} close={closeModalBool}>
|
||||
<Content>
|
||||
<div class="row" style="margin-bottom: 10px;">
|
||||
<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>
|
||||
</li>
|
||||
|
|
@ -166,17 +163,11 @@
|
|||
</html>
|
||||
|
||||
<style>
|
||||
@import "../customBootstrap.css";
|
||||
|
||||
html,
|
||||
:root {
|
||||
--main-txt-color: black;
|
||||
--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 {
|
||||
|
|
@ -230,18 +221,6 @@
|
|||
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 {
|
||||
color: var(--sub-txt-color);
|
||||
font-size: 0.8314159265358979323846264338rem;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,6 @@
|
|||
import {bearerFetch, jwt} from "../models/PomeloUtils";
|
||||
import {StrapiUserRepo} from "../models/repos/user/StrapiUserRepo";
|
||||
|
||||
/** @type {import('./$types').PageLoad} */
|
||||
export async function load() {
|
||||
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";
|
||||
}
|
||||
}
|
||||
// StrapiUserRepo.getInstance();
|
||||
}
|
||||
|
|
@ -1,24 +1,81 @@
|
|||
<script lang="ts">
|
||||
import type {Note} from "../../models/types";
|
||||
import {StrapiNoteRepository} from "../../models/repos/note/StrapiNoteRepository";
|
||||
import {onMount} from "svelte";
|
||||
|
||||
let notes: Note[] = JSON.parse(window.localStorage.getItem("notes"));
|
||||
const clickedNoteId = window.localStorage.getItem("clickedNoteId");
|
||||
let noteRepo: StrapiNoteRepository;
|
||||
let currentNote: Note;
|
||||
|
||||
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>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{"Pomelonote | Edit " + currNote.title}</title>
|
||||
<title>Editor</title>
|
||||
<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">
|
||||
</head>
|
||||
|
||||
<html lang="en">
|
||||
<div class="offset-3 col-6">
|
||||
{currNote.content}
|
||||
<div class="offset-3 col-6 wrapper">
|
||||
<h1 class="">{title === "" ? "" : title}</h1>
|
||||
<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>
|
||||
</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">
|
||||
import {setCookie} from "nookies";
|
||||
import type {Authentication} from "./models/authentication";
|
||||
import { SvelteToast } from '@zerodevx/svelte-toast'
|
||||
import {createErrorToast} from "../../models/customToasts";
|
||||
import {SvelteToast} from '@zerodevx/svelte-toast'
|
||||
import logo from "../../resources/images/logo2.svg";
|
||||
import {handleErrorsFromResponseWithToast} from "../../models/PomeloUtils";
|
||||
import {StrapiUserRepo} from "../../models/repos/user/StrapiUserRepo";
|
||||
|
||||
let user: string;
|
||||
let password: string;
|
||||
|
|
@ -14,31 +14,12 @@
|
|||
* Handles the button click.
|
||||
*/
|
||||
async function handleSubmit() {
|
||||
const endpoint = "http://localhost:1337/api/auth/local";
|
||||
const payload = {
|
||||
identifier: user,
|
||||
password: password
|
||||
};
|
||||
const userRepo: StrapiUserRepo = StrapiUserRepo.getInstance(false);
|
||||
|
||||
const login = await fetch(endpoint, {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
const response = await userRepo.loginUser(user, password);
|
||||
|
||||
const response: Authentication = await login.json();
|
||||
|
||||
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);
|
||||
}
|
||||
if (response.error != null) {
|
||||
handleErrorsFromResponseWithToast(response);
|
||||
} else {
|
||||
if (rememberMe) {
|
||||
setCookie(null, 'jwt', response.jwt, {
|
||||
|
|
@ -56,34 +37,34 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<title>PomeloNote | Login</title>
|
||||
<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">
|
||||
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css"
|
||||
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class="form-signin w-100 m-auto">
|
||||
|
||||
<img class="img-fluid" src="{logo}" alt="Logo">
|
||||
<img alt="Logo" class="img-fluid" src="{logo}">
|
||||
<h1 class="h3 mb-3 fw-normal">Please sign in</h1>
|
||||
|
||||
<div class="form-floating">
|
||||
<input type="email" class="form-control" id="floatingInput" placeholder="name@example.com" bind:value={user}>
|
||||
<input bind:value={user} class="form-control" id="floatingInput" placeholder="name@example.com" type="text">
|
||||
<label for="floatingInput">Email address or username</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input type="password" class="form-control" id="floatingPassword" placeholder="Password" bind:value={password}>
|
||||
<input bind:value={password} class="form-control" id="floatingPassword" placeholder="Password" type="password">
|
||||
<label for="floatingPassword">Password</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox mb-3">
|
||||
<label>
|
||||
<input type="checkbox" value="rememberMe" bind:checked={rememberMe}> Remember me
|
||||
<input bind:checked={rememberMe} type="checkbox" value="rememberMe"> Remember me
|
||||
</label>
|
||||
</div>
|
||||
<button class="w-100 btn btn-lg btn-primary" on:click={handleSubmit}>Sign in</button>
|
||||
<a href="/register" class="opacity-75 d-flex justify-content-center text-center fs-6">No user yet? Register.</a>
|
||||
<a class="opacity-75 d-flex justify-content-center text-center fs-6" href="/register">No user yet? Register.</a>
|
||||
<p class="mt-5 mb-3 text-muted">©2022</p>
|
||||
|
||||
</main>
|
||||
|
|
@ -91,42 +72,7 @@
|
|||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
<style>
|
||||
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="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;
|
||||
}
|
||||
@import "../../userInput.css";
|
||||
@import "../../customBootstrap.css";
|
||||
</style>
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import logo from "../../resources/images/logo2.svg";
|
||||
import {SvelteToast} from "@zerodevx/svelte-toast";
|
||||
import type {Authentication} from "../login/models/authentication";
|
||||
import {createErrorToast} from "../../models/customToasts";
|
||||
import {StrapiUserRepo} from "../../models/repos/user/StrapiUserRepo";
|
||||
import {handleErrorsFromResponseWithToast} from "../../models/PomeloUtils";
|
||||
|
||||
let user: string;
|
||||
let password: string;
|
||||
|
|
@ -13,32 +13,12 @@
|
|||
* Handles the button click.
|
||||
*/
|
||||
async function handleSubmit() {
|
||||
const endpoint = "http://localhost:1337/api/auth/local/register";
|
||||
const payload = {
|
||||
email: email,
|
||||
password: password,
|
||||
username: user
|
||||
};
|
||||
const userRepo: StrapiUserRepo = StrapiUserRepo.getInstance(false);
|
||||
|
||||
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();
|
||||
const response = await userRepo.registerUser(email, user, password);
|
||||
|
||||
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);
|
||||
}
|
||||
handleErrorsFromResponseWithToast(response);
|
||||
} else {
|
||||
window.location = "/login";
|
||||
}
|
||||
|
|
@ -49,29 +29,29 @@
|
|||
lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<title>PomeloNote | Register</title>
|
||||
<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">
|
||||
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css"
|
||||
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class="form-signin w-100 m-auto">
|
||||
|
||||
<img class="img-fluid" src="{logo}" alt="Logo">
|
||||
<img alt="Logo" class="img-fluid" src="{logo}">
|
||||
<h1 class="h3 mb-3 fw-normal">Register a new user</h1>
|
||||
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" id="floatingUsr" placeholder="exampleUsername" bind:value={user}>
|
||||
<input bind:value={user} class="form-control" id="floatingUsr" placeholder="exampleUsername" type="text">
|
||||
<label for="floatingUsr">Username</label>
|
||||
</div>
|
||||
|
||||
<div class="form-floating">
|
||||
<input type="email" class="form-control" id="floatingInput" placeholder="name@example.com" bind:value={email}>
|
||||
<input bind:value={email} class="form-control" id="floatingInput" placeholder="name@example.com" type="email">
|
||||
<label for="floatingInput">Email address</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input type="password" class="form-control" id="floatingPassword" placeholder="Password" bind:value={password}>
|
||||
<input bind:value={password} class="form-control" id="floatingPassword" placeholder="Password" type="password">
|
||||
<label for="floatingPassword">Password</label>
|
||||
</div>
|
||||
|
||||
|
|
@ -79,7 +59,7 @@
|
|||
Register user
|
||||
{#if user}: {user} {/if}
|
||||
</button>
|
||||
<a href="/login" class="opacity-75 d-flex justify-content-center text-center fs-6">Already registered? Login.</a>
|
||||
<a class="opacity-75 d-flex justify-content-center text-center fs-6" href="/login">Already registered? Login.</a>
|
||||
<p class="mt-5 mb-3 text-muted">©2022</p>
|
||||
|
||||
</main>
|
||||
|
|
@ -88,46 +68,6 @@
|
|||
</html>
|
||||
|
||||
<style>
|
||||
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;
|
||||
}
|
||||
@import "../../userInput.css";
|
||||
@import "../../customBootstrap.css";
|
||||
</style>
|
||||
78
frontend/svelte/src/service-worker.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/// <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);
|
||||
})()
|
||||
);
|
||||
}
|
||||
});
|
||||
7
frontend/svelte/src/stores.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
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)));
|
||||
}
|
||||
41
frontend/svelte/src/userInput.css
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
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;
|
||||
}
|
||||
41
frontend/svelte/static/manifest.json
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"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,7 +2,14 @@ import { sveltekit } from '@sveltejs/kit/vite';
|
|||
import type { UserConfig } from 'vite';
|
||||
|
||||
const config: UserConfig = {
|
||||
plugins: [sveltekit()]
|
||||
plugins: [sveltekit()],
|
||||
|
||||
server: {
|
||||
fs: {
|
||||
// Allow serving files from one level up to the project root
|
||||
allow: ['..'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||