Skip to content

Commit

Permalink
Merge pull request #263 from codex-team/note-settings-style-fix
Browse files Browse the repository at this point in the history
feat(store): session and persistant stores implemented
  • Loading branch information
e11sy authored Jul 17, 2024
2 parents 7b00e6b + b4b1dca commit 176d5f3
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 54 deletions.
17 changes: 12 additions & 5 deletions src/application/services/useAuthRequired.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useAppState } from './useAppState.ts';
import useAuth from './useAuth.ts';
import { useRouter } from 'vue-router';
import { until } from '@vueuse/core';

/**
* Function that is used in App for checking user authorization
Expand All @@ -14,19 +15,25 @@ export default function useAuthRequired(): void {

/**
* Check if user is authorized
* If authorization is in process we treat user as unauthorized
* When oauth will work, it will be treated as he authorized manually
* @returns true if user is authorized, false otherwise
*/
function isUserAuthorized(): boolean {
return (user.value !== null && user.value !== undefined);
async function isUserAuthorized(): Promise<boolean> {
/**
* Wait until authorization process is finished
*/
await until(user).not.toBe(undefined);

return user.value !== null;
}

/**
* For each route check if auth is required
*/
router.beforeEach((actualRoute, _, next) => {
if (actualRoute.meta.authRequired === true && !isUserAuthorized()) {
router.beforeEach(async (actualRoute, _, next) => {
const isAuthorized = await isUserAuthorized();

if (actualRoute.meta.authRequired === true && !isAuthorized) {
/**
* If auth is required and user is not autorized
* Then show google auth popup and redirect user to auth page
Expand Down
5 changes: 4 additions & 1 deletion src/infrastructure/auth.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type AuthRepositoryInterface from '@/domain/auth.repository.interface';
import type NotesApiTransport from './transport/notes-api';
import type AuthStorage from './storage/auth';
import type AuthSession from '@/domain/entities/AuthSession';
import { notEmpty } from './utils/empty';

/**
* Facade for the auth data
Expand All @@ -28,7 +29,9 @@ export default class AuthRepository implements AuthRepositoryInterface {
* Specify whether we have auth session (refresh token)
*/
public hasSession(): boolean {
return this.authStorage.getRefreshToken() !== null;
const refreshToken = this.authStorage.getRefreshToken();

return notEmpty(refreshToken);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/infrastructure/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ export function init(noteApiUrl: string, eventBus: EventBus): Repositories {
* Init storages
*/
const noteStore = new NoteStore();
const authStore = new AuthStore();
const userStore = new UserStore();
const editorToolsStore = new EditorToolsStore();
const openedPagesStore = new OpenedPagesStore();
const authStore = new AuthStore();

/**
* Init transport
Expand Down
82 changes: 82 additions & 0 deletions src/infrastructure/storage/abstract/persistant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/* eslint-disable-next-line boundaries/element-types */
import { SubscribableStore } from './subscribable';

export class PersistantStore<StoreData extends Record<string, unknown>> extends SubscribableStore<StoreData> {
/**
* Keys of the StoreData type
* Used to separate the needed local storage information
*/
protected readonly keysStored: string[];

/**
* Proxy for data stored
*/
protected data: StoreData;

/**
* Storage that would retain information when proxy is cleared
*/
private storage = window.localStorage;

constructor(keysStored: string[]) {
super();
this.keysStored = keysStored;
this.data = new Proxy<StoreData>({} as StoreData, this._data);

/**
* Load data from local store to proxy
*/
this.loadInitialData();
};

protected get _data(): ProxyHandler<StoreData> {
return {
get: (target, prop) => {
const item = this.storage.getItem(prop as string);

return item !== null ? JSON.parse(item) : undefined;
},

set: (target, prop, value, receiver) => {
/**
* Set new property value for proxy usage
*/
Reflect.set(target, prop, value, receiver);

/**
* Set new property value for storage
*/
this.storage.setItem(prop as string, JSON.stringify(value));

this.onDataChange([{ prop: prop as keyof StoreData,
newValue: value }]);

return true;
},

deleteProperty: (target, prop) => {
this.storage.removeItem(prop as string);

return Reflect.deleteProperty(target, prop);
},
};
}

/**
* Funciton for loading initial data from window.localStorage to proxy object
* Data stored in localStorage would be normalized for proxy
*/
private loadInitialData(): void {
for (let i = 0; i < this.storage.length; i++) {
const key = this.storage.key(i);

if (key !== null && this.keysStored.includes(key)) {
const storedValue = this.storage.getItem(key);

if (storedValue !== null) {
this.data[key as keyof StoreData] = JSON.parse(storedValue);
}
}
}
}
}
33 changes: 33 additions & 0 deletions src/infrastructure/storage/abstract/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* eslint-disable-next-line boundaries/element-types */
import { SubscribableStore } from './subscribable';

export class SessionStore<StoreData extends Record<string, unknown>> extends SubscribableStore<StoreData> {
/**
* Proxy for data stored in store.
* Used to watch data changes
*/
protected data = new Proxy<StoreData>({} as StoreData, this._data);

/**
* Data proxy handler
*/
protected get _data(): ProxyHandler<StoreData> {
return {
set: (target, prop, value, receiver) => {
Reflect.set(target, prop, value, receiver);

this.onDataChange([{ prop: prop as keyof StoreData,
newValue: value }]);

return true;
},
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
},

deleteProperty: (target, prop) => {
return Reflect.deleteProperty(target, prop);
},
};
}
}
31 changes: 3 additions & 28 deletions src/infrastructure/storage/abstract/subscribable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,40 +24,15 @@ type Change<StoreData> = {
* Allows to subscribe to store data changes
*/
export abstract class SubscribableStore<StoreData extends Record<string, unknown>> {
/**
* Proxy for data stored in store.
* Used to watch data changes
*/
protected data = new Proxy<StoreData>({} as StoreData, this._data);

/**
* List of subscribers on data change
*/
private subscribers: PropChangeCallback<StoreData>[] = [];
protected subscribers: PropChangeCallback<StoreData>[] = [];

/**
* Using for accumulation of changes in store until subscriber appearse
*/
private stashedChanges: Change<StoreData>[] = [];

/**
* Data proxy handler
*/
protected get _data(): ProxyHandler<StoreData> {
return {
set: (target, prop, value, receiver) => {
Reflect.set(target, prop, value, receiver);

this.onDataChange([{ prop: prop as keyof StoreData,
newValue: value }]);

return true;
},
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
},
};
}
protected stashedChanges: Change<StoreData>[] = [];

/**
* Subscribe to store changes
Expand Down Expand Up @@ -86,7 +61,7 @@ export abstract class SubscribableStore<StoreData extends Record<string, unknown
* Notifies subscribers about data change.
* @param changes - array of changes
*/
private onDataChange(changes: Change<StoreData>[]): void {
protected onDataChange(changes: Change<StoreData>[]): void {
/**
* If there are no subscribers stash current change
*/
Expand Down
27 changes: 14 additions & 13 deletions src/infrastructure/storage/auth.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
/**
* Stores auth data in local storage
*/
export default class AuthStorage {
/**
* Storage implementation
*/
private readonly storage: Storage;
import { PersistantStore } from './abstract/persistant';

export type AuthStoreData = {
/**
* Creates storage instance
* If user is authorized refresh token will be stored
*/
refreshToken?: string;
};

/**
* Stores auth data in local storage
*/
export default class AuthStorage extends PersistantStore<AuthStoreData> {
constructor() {
this.storage = window.localStorage;
super(['refreshToken']);
}

/**
* Returns refresh token
*/
public getRefreshToken(): string | null {
return this.storage.getItem('refreshToken');
return this.data.refreshToken === undefined ? null : this.data.refreshToken;
}

/**
* Save refresh token
* @param refreshToken - refresh token to save
*/
public setRefreshToken(refreshToken: string): void {
this.storage.setItem('refreshToken', refreshToken);
this.data.refreshToken = refreshToken;
}

/**
* Removes refresh token
*/
public removeRefreshToken(): void {
this.storage.removeItem('refreshToken');
delete this.data.refreshToken;
}
}
7 changes: 3 additions & 4 deletions src/infrastructure/storage/openedPage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { OpenedPage } from '@/domain/entities/OpenedPage';
import { SubscribableStore } from './abstract/subscribable';
import { PersistantStore } from './abstract/persistant';

export type OpenedPagesStoreData = {
/**
Expand All @@ -11,10 +11,9 @@ export type OpenedPagesStoreData = {
/**
* Class to store all pages that are currently opened in workspace
*/
export class OpenedPagesStore extends SubscribableStore<OpenedPagesStoreData> {
export class OpenedPagesStore extends PersistantStore<OpenedPagesStoreData> {
constructor() {
super();
this.data.openedPages = [];
super(['openedPages']);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/infrastructure/storage/user.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { User } from '@/domain/entities/User';
import { SubscribableStore } from './abstract/subscribable';
import { SessionStore } from './abstract/session';
import type EditorTool from '@/domain/entities/EditorTool';

/**
Expand All @@ -20,7 +20,7 @@ export type UserStoreData = {
/**
* Store for the user data
*/
export class UserStore extends SubscribableStore<UserStoreData> {
export class UserStore extends SessionStore<UserStoreData> {
/**
* Returns user data
*/
Expand Down

0 comments on commit 176d5f3

Please sign in to comment.