small works
Some checks failed
CI / Lint (push) Failing after 7m46s
CI / Test (push) Failing after 8m24s

This commit is contained in:
2025-04-04 18:21:37 +02:00
parent 211393728d
commit 0b29bc2d44
7 changed files with 297 additions and 31 deletions

View File

@@ -5,14 +5,21 @@ import { inject as service } from '@ember/service';
export default class ApplicationAdapter extends JSONAPIAdapter {
@service session;
namespace = 'api/v1';
get namespace() {
if (this.session.isAuthenticated) {
return 'api/v1';
} else {
return 'api/v1/public';
}
}
@computed('session.{data.authenticated.access_token,isAuthenticated}')
get headers() {
let headers = {};
if (this.session.isAuthenticated) {
// OAuth 2
headers['Authorization'] = `Bearer ${this.session.data.authenticated.access_token}`;
headers['Authorization'] =
`Bearer ${this.session.data.authenticated.access_token}`;
}
return headers;

View File

@@ -82,7 +82,7 @@ export default BaseAuthenticator.extend({
*/
restore(data) {
if (this._refreshTokenTimeout) {
run.cancel(this._refreshTokenTimeout);
cancel(this._refreshTokenTimeout);
delete this._refreshTokenTimeout;
}
return this._refreshAccessToken(data);
@@ -106,13 +106,14 @@ export default BaseAuthenticator.extend({
*/
authenticate(identification, password) {
return new RSVP.Promise((resolve, reject) => {
const {
identificationAttributeName,
passwordAttributeName
} = this.getProperties('identificationAttributeName', 'passwordAttributeName');
const { identificationAttributeName, passwordAttributeName } =
this.getProperties(
'identificationAttributeName',
'passwordAttributeName',
);
const data = {
[identificationAttributeName]: identification,
[passwordAttributeName]: password
[passwordAttributeName]: password,
};
const serverTokenEndpoint = this.get('serverTokenEndpoint');
@@ -141,7 +142,7 @@ export default BaseAuthenticator.extend({
@public
*/
invalidate() {
run.cancel(this._refreshTokenTimeout);
cancel(this._refreshTokenTimeout);
delete this._refreshTokenTimeout;
return RSVP.Promise.resolve();
},
@@ -161,22 +162,24 @@ export default BaseAuthenticator.extend({
const options = {
body: JSON.stringify(data),
headers,
method: 'POST'
method: 'POST',
};
return new RSVP.Promise((resolve, reject) => {
fetch(url, options).then((response) => {
response.text().then((text) => {
let json = text ? JSON.parse(text) : {};
if (!response.ok) {
response.responseJSON = json;
reject(response);
} else {
window.localStorage.setItem('jwtLastRefreshAt', Date.now());
resolve(json);
}
});
}).catch(reject);
fetch(url, options)
.then((response) => {
response.text().then((text) => {
let json = text ? JSON.parse(text) : {};
if (!response.ok) {
response.responseJSON = json;
reject(response);
} else {
window.localStorage.setItem('jwtLastRefreshAt', Date.now());
resolve(json);
}
});
})
.catch(reject);
});
},
@@ -219,7 +222,7 @@ export default BaseAuthenticator.extend({
const offset = 1000; // Refresh 1 sec before JWT expires
const now = Date.now();
const waitMs = (jwtPayloadExpiresAt * 1000) - now - offset; //expiresAt is in sec
const waitMs = jwtPayloadExpiresAt * 1000 - now - offset; //expiresAt is in sec
if (this._refreshTokenTimeout) {
cancel(this._refreshTokenTimeout);
@@ -228,12 +231,18 @@ export default BaseAuthenticator.extend({
// Reschedule if the JWT is still valid
if (waitMs > 0) {
this._refreshTokenTimeout = later(this, this._refreshAccessToken, data, waitMs);
this._refreshTokenTimeout = later(
this,
this._refreshAccessToken,
data,
waitMs,
);
}
},
_refreshAccessToken(data) {
var timeElapsedSinceLastRefresh = Date.now() - window.localStorage.getItem('jwtLastRefreshAt')
var timeElapsedSinceLastRefresh =
Date.now() - window.localStorage.getItem('jwtLastRefreshAt');
if (timeElapsedSinceLastRefresh <= this.get('refreshTokenAfter')) {
// Request attempted too soon! Reschedule
return this._validateTokenAndScheduleRefresh(data);
@@ -242,7 +251,9 @@ export default BaseAuthenticator.extend({
const serverRefreshTokenEndpoint = this.get('serverRefreshTokenEndpoint');
return new RSVP.Promise((resolve, reject) => {
this.makeRequest(serverRefreshTokenEndpoint, data, { Authorization:data["access_token"] })
this.makeRequest(serverRefreshTokenEndpoint, data, {
Authorization: data['access_token'],
})
.then((response) => {
return this._validateTokenAndScheduleRefresh(response);
})
@@ -257,7 +268,7 @@ export default BaseAuthenticator.extend({
reason = JSON.stringify(reason.responseJSON);
}
warn(`JWT token could not be refreshed: ${reason}.`, false, {
id: 'ember-simple-auth-jwt.failedJWTTokenRefresh'
id: 'ember-simple-auth-jwt.failedJWTTokenRefresh',
});
reject();
@@ -267,10 +278,12 @@ export default BaseAuthenticator.extend({
_validateTokenAndScheduleRefresh(response) {
if (!this._validate(response)) {
return RSVP.Promise.reject('token is missing or invalid in server response');
return RSVP.Promise.reject(
'token is missing or invalid in server response',
);
}
this._scheduleTokenRefresh(response);
return RSVP.Promise.resolve(response);
}
},
});

View File

@@ -0,0 +1,2 @@
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
<a href={{@href}} class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white" aria-current="page">{{@title}}</a>

View File

@@ -0,0 +1,218 @@
<nav class="bg-gray-800">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="border-b border-gray-700">
<div class="flex h-16 items-center justify-between px-4 sm:px-0">
<div class="flex items-center">
<div class="shrink-0">
<img
class="size-8"
src="https://tailwindui.com/plus-assets/img/logos/mark.svg?color=indigo&shade=500"
alt="Your Company"
/>
</div>
<div class="hidden md:block">
<div class="ml-10 flex items-baseline space-x-4">
<Topbar::Item @title="Dashboard" @href="#" />
<Topbar::Item @title="Team" @href="#" />
<Topbar::Item @title="Projects" @href="#" />
<Topbar::Item @title="Calendar" @href="#" />
<Topbar::Item @title="Reports" @href="#" />
</div>
</div>
</div>
<div class="hidden md:block">
<div class="ml-4 flex items-center md:ml-6">
<button
type="button"
class="relative rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
>
<span class="absolute -inset-1.5"></span>
<span class="sr-only">View notifications</span>
<svg
class="size-6"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true"
data-slot="icon"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0"
/>
</svg>
</button>
<!-- Profile dropdown -->
<div class="relative ml-3">
<div>
<button
type="button"
class="relative flex max-w-xs items-center rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
id="user-menu-button"
aria-expanded="false"
aria-haspopup="true"
>
<span class="absolute -inset-1.5"></span>
<span class="sr-only">Open user menu</span>
<img
class="size-8 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt=""
/>
</button>
</div>
<!--
Dropdown menu, show/hide based on menu state.
Entering: "transition ease-out duration-100"
From: "transform opacity-0 scale-95"
To: "transform opacity-100 scale-100"
Leaving: "transition ease-in duration-75"
From: "transform opacity-100 scale-100"
To: "transform opacity-0 scale-95"
-->
<div
class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none"
role="menu"
aria-orientation="vertical"
aria-labelledby="user-menu-button"
tabindex="-1"
>
<Topbar::ProfileItem @title="Your Profile" />
<Topbar::ProfileItem @title="Settings" />
</div>
</div>
</div>
</div>
<div class="-mr-2 flex md:hidden">
<!-- Mobile menu button -->
<button
type="button"
class="relative inline-flex items-center justify-center rounded-md bg-gray-800 p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
aria-controls="mobile-menu"
aria-expanded="false"
>
<span class="absolute -inset-0.5"></span>
<span class="sr-only">Open main menu</span>
<!-- Menu open: "hidden", Menu closed: "block" -->
<svg
class="block size-6"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true"
data-slot="icon"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
/>
</svg>
<!-- Menu open: "block", Menu closed: "hidden" -->
<svg
class="hidden size-6"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true"
data-slot="icon"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6 18 18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- Mobile menu, show/hide based on menu state. -->
<div class="border-b border-gray-700 md:hidden" id="mobile-menu">
<div class="space-y-1 px-2 py-3 sm:px-3">
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
<a
href="#"
class="block rounded-md bg-gray-900 px-3 py-2 text-base font-medium text-white"
aria-current="page"
>Dashboard</a>
<a
href="#"
class="block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white"
>Team</a>
<a
href="#"
class="block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white"
>Projects</a>
<a
href="#"
class="block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white"
>Calendar</a>
<a
href="#"
class="block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white"
>Reports</a>
</div>
<div class="border-t border-gray-700 pb-3 pt-4">
<div class="flex items-center px-5">
<div class="shrink-0">
<img
class="size-10 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt=""
/>
</div>
<div class="ml-3">
<div class="text-base/5 font-medium text-white">Tom Cook</div>
<div class="text-sm font-medium text-gray-400">tom@example.com</div>
</div>
<button
type="button"
class="relative ml-auto shrink-0 rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
>
<span class="absolute -inset-1.5"></span>
<span class="sr-only">View notifications</span>
<svg
class="size-6"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true"
data-slot="icon"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0"
/>
</svg>
</button>
</div>
<div class="mt-3 space-y-1 px-2">
<a
href="#"
class="block rounded-md px-3 py-2 text-base font-medium text-gray-400 hover:bg-gray-700 hover:text-white"
>Your Profile</a>
<a
href="#"
class="block rounded-md px-3 py-2 text-base font-medium text-gray-400 hover:bg-gray-700 hover:text-white"
>Settings</a>
<a
href="#"
class="block rounded-md px-3 py-2 text-base font-medium text-gray-400 hover:bg-gray-700 hover:text-white"
>Sign out</a>
</div>
</div>
</div>
</nav>

View File

@@ -0,0 +1,8 @@
<!-- Active: "bg-gray-100 outline-none", Not Active: "" -->
<a
href="#"
class="block px-4 py-2 text-sm text-gray-700"
role="menuitem"
tabindex="-1"
id="user-menu-item-0"
>{{@title}}</a>

View File

@@ -5,6 +5,7 @@
<title>Holygamesapp</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
{{content-for "head"}}

View File

@@ -7,6 +7,23 @@
<LinkTo @route="login">Login</LinkTo>
{{/if}}
</div>
<div class="main">
{{outlet}}
<div class="min-h-full">
<div class="bg-gray-800 pb-32">
<Topbar::Navbar />
<header class="py-10">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<h1 class="text-3xl font-bold tracking-tight text-white">Dashboard</h1>
</div>
</header>
</div>
<main class="-mt-32">
<div class="mx-auto max-w-7xl px-4 pb-12 sm:px-6 lg:px-8">
<div class="rounded-lg bg-white px-5 py-6 shadow sm:px-6">
{{outlet}}
</div>
</div>
</main>
</div>