Skip to content

Commit

Permalink
feat: Adding ability to use a fake API instead of running the one in …
Browse files Browse the repository at this point in the history
…this repo (#52)

- Fixing issue with GET User not working properly
- Adding IViewModel, so we can make a dynamic interface from a class
  • Loading branch information
incutonez authored May 22, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 0b8c8fd commit 5928b73
Showing 7 changed files with 120 additions and 15 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -16,4 +16,4 @@ There is a [script](https://github.com/incutonez/Sandbox/blob/main/updateDepende

# Examples

The `ui` dir in this repo gets deployed to [GH Pages](https://pages.github.com/). Here is the link to the [Zelda 1 World Editor](http://incutonez.github.io/Sandbox/#/zelda). The other two options in the navigation bar don't work because they rely on the API, which can't be deployed to GH Pages.
The `ui` dir in this repo gets deployed to [GH Pages](https://pages.github.com/). Here is the link to the [Zelda 1 World Editor](http://incutonez.github.io/Sandbox/#/zelda). The other two options in the navigation bar work, but don't have a proper API, which can't be deployed to GH Pages.
Binary file modified api/src/db/data.db
Binary file not shown.
1 change: 1 addition & 0 deletions api/src/users/users.service.ts
Original file line number Diff line number Diff line change
@@ -36,6 +36,7 @@ export class UsersService {

async getUser(userId: string) {
const response = await User.findOne({
raw: true,
where: {
id: userId,
},
2 changes: 1 addition & 1 deletion linkedIn/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* In order to use this, go to about:debugging#/runtime/this-firefox and load the manifest.json file as the Temporary Add-on
*/
const MatchRegex = /Viewed|Applied/;
const MatchRegex = /Viewed|Applied|Promoted/;
const CompaniesRegex = /DataAnnotation|Veeva Systems|Aha!|HireMeFast|Team Remotely|Recruiting from Scratch|myGwork/i;

setInterval(removeJobs, 100);
97 changes: 87 additions & 10 deletions ui/src/models/UserModel.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,92 @@
import { toRaw } from "vue";
import { faker } from "@faker-js/faker";
import { ApiPaginatedRequest, UserEntity, UsersApi } from "@incutonez/spec/dist";
import { Allow, IsInt, IsString } from "class-validator";
import { configuration } from "@/apiConfig";
import { HasAPI } from "@/enums/helper";
import { IsRequired } from "@/models/decorators";
import { ViewModel } from "@/models/ViewModel";
import { removeItem } from "@/utils/common";

export const UsersAPI = new UsersApi(configuration);

const LocalUsers: UserModel[] = [];

async function loadUsers(request: ApiPaginatedRequest) {
if (HasAPI) {
return UsersAPI.listUsers(request);
}
for (let i = request.start; i < request.limit + request.start; i++) {
LocalUsers[i] ??= UserModel.create({
id: faker.string.uuid(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
email: faker.internet.email(),
phone: faker.phone.number(),
birthDate: faker.date.birthdate().getTime(),
gender: faker.person.gender(),
});
}
return {
data: {
total: LocalUsers.length < 500 ? 500 : LocalUsers.length,
data: LocalUsers.slice(request.start, request.start + request.limit),
},
};
}

async function loadUser(userId: string) {
if (HasAPI) {
return UsersAPI.getUser(userId);
}
return {
data: LocalUsers.find((user) => user?.id === userId),
};
}

async function createUser(user: UserModel) {
if (HasAPI) {
return UsersAPI.createUser(user.get());
}
LocalUsers.push(user);
user.id = faker.string.uuid();
return {
data: user.get(),
};
}

async function updateUser(user: UserModel) {
if (HasAPI) {
return UsersAPI.updateUser(user.id, user.get());
}
const found = LocalUsers.find((item) => item?.id === user.id);
if (found) {
found.set(user.get());
}
return {
data: found?.get(),
};
}

async function deleteUser(user: UserModel) {
if (HasAPI) {
return UsersAPI.deleteUser(user.id);
}
removeItem(LocalUsers, toRaw(user));
}

async function copyUser(user: UserModel) {
if (HasAPI) {
return UsersAPI.copyUser(user.id);
}
const clone = user.clone();
clone.id = faker.string.uuid();
LocalUsers.push(clone);
return {
data: clone,
};
}

export class UserModel extends ViewModel implements UserEntity {
@Allow()
id = "";
@@ -37,35 +117,32 @@ export class UserModel extends ViewModel implements UserEntity {
}

static async readAll(request: ApiPaginatedRequest) {
const response = HasAPI
? await UsersAPI.listUsers(request)
: {
data: [],
};
const response = await loadUsers(request);
return super._readAll(response.data);
}

async read(userId = this.id) {
const response = await UsersAPI.getUser(userId);
console.log("reading", userId);
const response = await loadUser(userId);
return response.data;
}

async create() {
const response = await UsersAPI.createUser(this.get());
const response = await createUser(this);
return response.data;
}

async update() {
const response = await UsersAPI.updateUser(this.id, this.get());
const response = await updateUser(this);
return response.data;
}

async delete() {
await UsersAPI.deleteUser(this.id);
await deleteUser(this);
}

async copy() {
const response = await UsersAPI.copyUser(this.id);
const response = await copyUser(this);
return response.data;
}
}
31 changes: 29 additions & 2 deletions ui/src/models/ViewModel.ts
Original file line number Diff line number Diff line change
@@ -4,14 +4,36 @@ import { validate, ValidatorOptions } from "class-validator";
import { unset } from "lodash-es";
import { getObjectValue, isEmpty, isObject } from "@/utils/common";

// Related: https://github.com/microsoft/TypeScript/issues/42896#issuecomment-782754005
type IfEquals<X, Y, A = X, B = never> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? A : B;

type WritableKeys<T> = {
[P in keyof T]-?:
IfEquals<
{ [Q in P]: T[P] },
{ -readonly [Q in P]: T[P] },
P
>
}[keyof T];

export type ModelInterface<T> = {
// We need to map over the keys directly to preserve optionality. We filter with "as"
// Exclude undefined from the check to properly handle optional properties
// eslint-disable-next-line @typescript-eslint/ban-types
[K in keyof T as T[K] extends Function ? never : K extends Symbol ? never : K]: Exclude<T[K], undefined> extends Array<infer E> ? Array<ModelInterface<E>> : Exclude<T[K], undefined> extends Record<string, never> ? ModelInterface<T[K]> : T[K];
};

/**
* The types above are a little confusing... we want to remove getters and setters, and that seems only
* doable by using IfEquals and WritableKeys, which unfortunately removes all readonly properties (might be okay though).
* We also want to exclude functions and symbols, so that's why we use ModelInterface when defining IViewModel.
* Source:
* - https://stackoverflow.com/questions/49579094/typescript-conditional-types-filter-out-readonly-properties-pick-only-requir
* - https://github.com/microsoft/TypeScript/issues/42896#issuecomment-782754005
*/
export type IViewModel<T> = Pick<T, WritableKeys<ModelInterface<T>>>;

export interface IModelGetOptions extends ClassTransformOptions {
exclude?: string[];
}
@@ -171,7 +193,12 @@ export class ViewModel {
const genericResponse = "data" in response;
const data = genericResponse ? response.data : response;
if (data) {
records = (data as ViewModel[]).map((item) => this.create(item));
records = (data as ViewModel[]).map((item) => {
if (item[IsModel]) {
return item;
}
return this.create(item);
});
}
if (genericResponse) {
(response.data as unknown[]) = records;
2 changes: 1 addition & 1 deletion ui/src/utils/table.ts
Original file line number Diff line number Diff line change
@@ -272,7 +272,7 @@ export function useDataTable<T = unknown>(props: ITableGrid, emit: TTableEmit) {
}
/**
* If our loadCount is >= rowsPerPage, then that means we've loaded the range... we must check rowsPerPage because
* the max isn't sufficient enough, as it could just be the remoteMax value instead of how many rows we want to
* the max isn't sufficient, as it could just be the remoteMax value instead of how many rows we want to
* display. We also check to see if we've loaded < the max, as that means we've most likely hit the last row of
* results.
*/

0 comments on commit 5928b73

Please sign in to comment.