Skip to content

Commit

Permalink
Switched to typescript and removed LoadObject
Browse files Browse the repository at this point in the history
  • Loading branch information
jlkalberer committed Dec 24, 2024
1 parent 35d2924 commit 70c7f31
Show file tree
Hide file tree
Showing 60 changed files with 8,049 additions and 1,282 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ coverage
node_modules
stats.json
.vscode/
dist/

# Cruft
.DS_Store
Expand Down
8 changes: 8 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/**/*.test.ts'],
// setupFilesAfterEnv: ['<rootDir>/src/setup-jest.ts'],
openHandlesTimeout: 1000,
};
5,575 changes: 4,296 additions & 1,279 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
],
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"lint": "eslint src/ --fix --max-warnings 0",
"test": "ava",
"test:watch": "ava --watch",
"test": "jest",
"test:watch": "jest --watch",
"test:api": "babel-node ./test/index.js"
},
"devDependencies": {
Expand All @@ -25,7 +26,8 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-prettier": "^5.1.3",
"prettier": "3.2.5"
"jest": "^29.7.0",
"prettier": "^3.2.5"
},
"dependencies": {
"array-flatten": "^3.0.0",
Expand Down
120 changes: 120 additions & 0 deletions src/Auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import type { EntityID } from './types';

import fetch from './fetch';

export type UserCredentials = {
password: string;
userName: string;
};

export type UserRole = 'Administrator' | 'Super Administator';

export type AuthResponse = {
accessToken: string;
email: string;
expiresAt: Date;
expiresIn: number;
id: EntityID;
issuedAt: Date;
phoneNumber: string;
refreshToken: string;
roles: Array<UserRole>;
tokenType: string;
userLogins: Array<string>;
userName: string;
};

export type ChangePasswordArgs = {
newPassword: string;
oldPassword: string;
};

export type RegisterArgs = {
email: string;
password: string;
userName: string;
};

type LoginResponse = {
email: string;
id: string;
phoneNumber: string;
userName: string;
access_token: string;
'.expires': Date;
expires_in: number;
'.issued': Date;
refresh_token: string;
roles: string;
token_type: string;
userLogins: string;
};

const reformatLoginResponse = (response: LoginResponse): AuthResponse => ({
...response,
accessToken: response.access_token,
expiresAt: response['.expires'],
expiresIn: response.expires_in,
issuedAt: response['.issued'],
refreshToken: response.refresh_token,
roles: JSON.parse(response.roles),
tokenType: response.token_type,
userLogins: JSON.parse(response.userLogins),
});

class AuthImpl {
changePassword(
changePasswordArgs: ChangePasswordArgs,
): Promise<Record<string, never>> {
return fetch('api/account/change-password/', {
body: JSON.stringify({
...changePasswordArgs,
confirmPassword: changePasswordArgs.newPassword,
}),
headers: [{ name: 'Content-type', value: 'application/json' }],
method: 'POST',
});
}

fetchRoles(): Promise<Array<UserRole>> {
return fetch('api/v2/roles/');
}

login({ password, userName }: UserCredentials): Promise<AuthResponse> {
return fetch<LoginResponse>('token/', {
body: `grant_type=password&userName=${userName}&password=${password}`,
headers: [
{ name: 'Content-type', value: 'application/x-www-form-urlencoded' },
],
method: 'POST',
}).then(reformatLoginResponse);
}

refreshToken(refreshToken: string): Promise<AuthResponse> {
return fetch<LoginResponse>('token/', {
body: `grant_type=refresh_token&refresh_token=${refreshToken}`,
headers: [
{ name: 'Content-type', value: 'application/x-www-form-urlencoded' },
],
method: 'POST',
}).then(reformatLoginResponse);
}

register(registerArgs: RegisterArgs): Promise<void> {
return fetch('api/account/register/', {
body: JSON.stringify(registerArgs),
headers: [{ name: 'Content-type', value: 'application/json' }],
method: 'POST',
});
}

resetPassword(email: string): Promise<void> {
return fetch('api/account/reset-password/', {
body: JSON.stringify({ email }),
headers: [{ name: 'Content-type', value: 'application/json' }],
method: 'POST',
});
}
}

export const Auth = new AuthImpl();
90 changes: 90 additions & 0 deletions src/CloudSSEManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import Subscription from './dao/Subscription';
import nullthrows from 'nullthrows';
import Config from './Config';

export type SSESubscriptionOptions = {
eventNamePrefix?: string;
onError?: (event: MessageEvent) => unknown;
onOpen?: (event: MessageEvent) => unknown;
particleId?: string;
};

export type CloudEvent = {
data: Record<string, never>;
name: string;
particleId: string;
publishedAt: Date;
};

type SSEHandler = (event: CloudEvent) => void;

class CloudSSEManager extends Subscription {
static _sessionByHandler: Map<SSEHandler, EventSource> = new Map();

static subscribe(
handler: SSEHandler,
subscribeOptions: SSESubscriptionOptions,
) {
const { onError, onOpen } = subscribeOptions;

const session = new EventSource(CloudSSEManager._getUrl(subscribeOptions));

session.addEventListener('message', (sseEvent: MessageEvent): void => {
try {
const cloudEventStr = sseEvent.data;
const cloudEvent = JSON.parse(cloudEventStr);

const {
name,
coreid: particleId,
data,
published_at: publishedAt,
} = cloudEvent;

handler({ data, name, particleId, publishedAt });
} catch (error: unknown) {
if (error instanceof Error) {
CloudSSEManager.__emitError(error);
if (onError) {
onError(sseEvent);
}
}
}
});

if (onOpen) {
session.addEventListener('open', onOpen);
}

if (onError) {
session.addEventListener('error', onError);
}

session.addEventListener('error', (event: Event) => {
CloudSSEManager.__emitError(new Error(JSON.stringify(event)));
});

CloudSSEManager._sessionByHandler.set(handler, session);
}

static unsubscribe(handler: SSEHandler) {
const session = CloudSSEManager._sessionByHandler.get(handler);
if (!session) {
return;
}
session.close();
CloudSSEManager._sessionByHandler.delete(handler);
}

static _getUrl({ eventNamePrefix = '', particleId }: SSESubscriptionOptions) {
const devicesUrl = particleId ? `devices/${particleId}/events/` : 'events/';

return `${nullthrows(
Config.host,
)}/api/v2/${devicesUrl}${eventNamePrefix}/?access_token=${nullthrows(
Config.token,
)}`;
}
}

export default CloudSSEManager;
11 changes: 11 additions & 0 deletions src/Config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type {EntityID} from './types';

class Config {
static host: string | null | undefined = null;

static organizationId: EntityID | null | undefined = null;

static token: string | null | undefined = null;
}

export default Config;
4 changes: 4 additions & 0 deletions src/StandardHeaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default [{
name: 'timezoneOffset',
value: new Date().getTimezoneOffset().toString(),
}, { name: 'Prefer', value: 'return=representation' }];
63 changes: 63 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type {EntityName} from './types';
import type { FilterOperator } from './filters';
import type { PermissionType } from './dao/PermissionDAO';

export const PERMISSIONS_MAP: Partial<Record<PermissionType, number>> = {
Administrator: 4,
BannedFromTap: 1,
Edit: 3,
Read: 2,
};

export const DAO_ENTITIES: {
[key: string]: EntityName
} = {
ACCOUNTS: 'accounts',
ACHIEVEMENTS: 'achievements',
BEVERAGE_AVAILABILITIES: 'beverage-availabilities',
BEVERAGE_GLASSES: 'beverage-glasses',
BEVERAGE_SRMS: 'beverage-srms',
BEVERAGE_STYLES: 'beverage-styles',
BEVERAGES: 'beverages',
DEVICES: 'devices',
FLOW_SENSORS: 'flow-sensors',
FRIENDS: 'friends',
KEGS: 'kegs',
LOCATIONS: 'locations',
ORGANIZATIONS: 'organizations',
PERMISSIONS: 'permissions',
POUR_CHART: 'chart',
POURS: 'pours',
PRICE_VARIANTS: 'price-variants',
REPORTS: 'reports',
SCHEDULE_GROUPS: 'schedule-groups',
SCHEDULES: 'schedules',
TAPS: 'taps',
};

const FILTER_OPERATORS: {
[key: string]: FilterOperator
} = {
ANY: 'any',
CONTAINS: 'contains',
ENDS_WITH: 'endswith',
EQUALS: 'eq',
GREATER_THAN: 'gt',
GREATER_THAN_OR_EQUAL: 'ge',
LESS_THAN: 'lt',
LESS_THAN_OR_EQUAL: 'le',
NOT_ENDS_WITH: 'not endswith',
NOT_EQUALS: 'ne',
NOT_STARTS_WITH: 'not startswith',
STARTS_WITH: 'startswith',
};

const FILTER_FUNCTION_OPERATORS = [
FILTER_OPERATORS.CONTAINS,
FILTER_OPERATORS.ENDS_WITH,
FILTER_OPERATORS.NOT_ENDS_WITH,
FILTER_OPERATORS.NOT_STARTS_WITH,
FILTER_OPERATORS.STARTS_WITH,
];

export { FILTER_OPERATORS, FILTER_FUNCTION_OPERATORS };
42 changes: 42 additions & 0 deletions src/dao/AccountDAO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { EntityID } from '../types';

import ODataDAO from './ODataDAO';
import { DAO_ENTITIES } from '../constants';
import DefaultTranslator from '../translators/DefaultTranslator';

export type Account = {
accessFailedCount: number | null | undefined;
banned: boolean | null | undefined;
createdDate: Date;
email: string | null | undefined;
emailConfirmed: boolean | null | undefined;
fullName: string | null | undefined;
id: EntityID;
lockoutEnabled: boolean | null | undefined;
lockoutEndDateUtc: string | null | undefined;
logins: Record<string, unknown> | null | undefined;
phoneNumber: string | null | undefined;
phoneNumberConfirmed: boolean | null | undefined;
roles: Record<string, unknown> | null | undefined;
twoFactorEnabled: boolean | null | undefined;
userName: string;
};

export type AccountMutator = {
email: string;
fullName?: string;
id?: EntityID;
phoneNumber: string;
userName: string;
};

class AccountDAOImpl extends ODataDAO<Account, AccountMutator> {
constructor() {
super({
entityName: DAO_ENTITIES.ACCOUNTS,
translator: new DefaultTranslator(),
});
}
}

export const AccountDAO = new AccountDAOImpl();
Loading

0 comments on commit 70c7f31

Please sign in to comment.