Skip to content
This repository has been archived by the owner on Apr 27, 2023. It is now read-only.

Commit

Permalink
Start CLI client
Browse files Browse the repository at this point in the history
  • Loading branch information
waldeck-dev committed Sep 28, 2022
1 parent b78a999 commit d4e0a78
Show file tree
Hide file tree
Showing 8 changed files with 348 additions and 0 deletions.
9 changes: 9 additions & 0 deletions cli/deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export { Command } from "https://deno.land/x/[email protected]/command/mod.ts";
export {
Confirm,
Input,
Secret,
Select,
} from "https://deno.land/x/[email protected]/prompt/mod.ts";
export type { SelectOptionSettings } from "https://deno.land/x/[email protected]/prompt/select.ts";
export { colors } from "https://deno.land/x/[email protected]/ansi/colors.ts";
19 changes: 19 additions & 0 deletions cli/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { IAuthResponse } from "./types/api.ts";
import { Auth } from "./ui/auth.ts";
import { MainMenu } from "./ui/main-menu.ts";
import { clear } from "./ui/utils.ts";

export let jwt = "";
export let user = {};

async function main() {
clear();

const data = await Auth() as IAuthResponse;
jwt = data.jwt;
user = data.user;

await MainMenu.show();
}

await main();
28 changes: 28 additions & 0 deletions cli/types/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export interface IAuthPayload {
email: string;
password: string;
}

export interface IAuthResponse {
jwt: string;
user: IUser;
}

export interface IUser {
id: number;
}

export interface IGroup {
id?: number;
name?: string;
owner?: number;
}

export interface IUserGroup {
id?: number;
confirmed?: boolean;
createdAt?: string;
updatedAt?: string;
blocked?: boolean;
group?: IGroup;
}
61 changes: 61 additions & 0 deletions cli/ui/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { colors, Input, Secret } from "../deps.ts";
import { clear } from "./utils.ts";
import { IAuthPayload, IAuthResponse } from "../types/api.ts";

let email: string,
password: string;

email = "[email protected]";
password = "Valentin74!";

async function authenticate({ email, password }: IAuthPayload) {
const res = await fetch("http://localhost:1337/api/auth/local", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ identifier: email, password }),
});

const resJson = await res.json();

if (resJson.error) {
const err = resJson.error;
return {
data: null,
error: colors.bold.red(`${err.name} (${err.status}): ${err.message}`),
};
}

return {
data: { jwt: resJson.jwt as string, user: resJson.user } as IAuthResponse,
error: false,
};
}

export async function Auth(
errorMessage?: string,
): Promise<IAuthResponse | void> {
clear(errorMessage);

const emailInput = Input;
emailInput.inject(email);
email = await emailInput.prompt({
message: "📧 Enter your email",
minLength: 1,
});

password = password ?? await Secret.prompt({
message: "🔒 Enter you password",
minLength: 1,
});

const { data, error } = await authenticate({ email, password });

if (error && typeof error === "string") {
password = "";
return await Auth(error);
}

return data as IAuthResponse;
}
124 changes: 124 additions & 0 deletions cli/ui/class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { Select, SelectOptionSettings } from "../deps.ts";
import { jwt } from "../mod.ts";

interface ICliViewOptions {
title?: string;
parent?: CliView;
prefetch?: IPrefetch;
options: (arg0: IOptionsOptions) => Promise<SelectOptionSettings[] | void>;
handleValue?: (value: string) => Promise<void>;
}

type CliViewParams = Record<string, unknown>;

interface IOptionsOptions {
params?: CliViewParams;
data: Record<string, unknown>;
}

interface IPrefetch {
url: string | ((params: CliViewParams) => string);
method?: string;
body?: Record<string, unknown>;
}

export class CliView {
title;
parent;
prefetch;
options;
handleValue;
params = {};

constructor(options: ICliViewOptions) {
this.title = options.title;
this.parent = options?.parent;
this.prefetch = options?.prefetch;
this.options = options.options;
this.handleValue = options?.handleValue;
}

async show(params?: CliViewParams) {
this.params = params ?? {};

this.clear();

const rawResponse = await this.fetchData(params);

const options = await this.options({ params, data: rawResponse });

const value = await Select.prompt({
message: this.getTitle(),
options: [
...(options || []),
...(
this.parent
? [
Select.separator("——————————"),
{ name: "⬅️ Go back", value: "back" },
]
: []
),
],
});

if (value === "back") {
if (this.parent) {
await this.parent?.show(this.parent?.params);
}
}

if (typeof this.handleValue === "function") {
await this.handleValue(value);
}
}

async fetchData(params?: CliViewParams) {
let rawResponse;

if (this.prefetch) {
const url = typeof this.prefetch.url === "function"
? this.prefetch.url({ ...params })
: this.prefetch.url;

const res = await fetch(url, {
method: this.prefetch.method ?? "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${jwt}`,
},
body: typeof this.prefetch.body === "object"
? JSON.stringify({ ...this.prefetch.body })
: undefined,
});

rawResponse = await res.json();
}

return rawResponse;
}

getTitle() {
let title = "";

if (this.parent) {
title += this.parent.getTitle() + " 〉";
}

return title + this.title;
}

clear(messages?: string | string[]) {
console.log("\x1Bc");

if (messages) {
if (typeof messages === "string") {
messages = [messages];
}

messages.forEach((msg) => {
console.error(msg);
});
}
}
}
79 changes: 79 additions & 0 deletions cli/ui/group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Confirm, Input, Select, SelectOptionSettings } from "../deps.ts";
import { IGroup, IUserGroup } from "../types/api.ts";
import { clear } from "./utils.ts";
import { jwt } from "../mod.ts";
import { CliView } from "./class.ts";

export const GroupMain = new CliView({
title: "Groups",
prefetch: {
url: "http://localhost:1337/api/user-groups",
},
options: async ({ data }) =>
await [
// @ts-ignore Data comming from api
...data.data.map((ug: IUserGroup) => ({
name: `- ${ug?.group?.name}`,
value: `${ug?.group?.id}`,
})),
Select.separator("——————————"),
{ name: "🔍 Join a Group", value: "search", disabled: true },
{ name: "➕ Create new Group", value: "new" },
],
handleValue: async (value) => {
if (typeof +value === "number" && !isNaN(+value)) {
await GroupDetails.show({ groupId: +value });
}
},
});

export const GroupDetails = new CliView({
title: "Users",
parent: GroupMain,
prefetch: {
url: (params) => {
return typeof params?.groupId === "number"
? `http://localhost:1337/api/groups/${+params.groupId}`
: "";
},
},
options: async ({ data }) => {
// @ts-ignore Data comming from api
const allUserGroups = data?.attributes["user-groups"];
if (!Array.isArray(allUserGroups)) return await GroupMain.show();
return allUserGroups.map((ug) => ({
name: ug?.user?.username,
value: "" + ug?.user?.id,
} as SelectOptionSettings));
},
});

export async function GroupForm(_group?: IGroup) {
clear();

const name = await Input.prompt({
message: "Enter group name",
minLength: 1,
});

const confirmed = await Confirm.prompt(
`Are you sure you want to create group ${name}?`,
);

if (confirmed) {
const res = await fetch("http://localhost:1337/api/groups", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${jwt}`,
},
body: JSON.stringify({ data: { name } }),
});

const resJson = await res.json();

if (typeof resJson?.data?.id === "number") {
await GroupMain.show();
}
}
}
15 changes: 15 additions & 0 deletions cli/ui/main-menu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { CliView } from "./class.ts";
import { GroupMain } from "./group.ts";

export const MainMenu = new CliView({
title: "Pronocup",
options: async () =>
await [
{ name: "👥 Groups", value: "group", disabled: false },
{ name: "⚽ Predictions", value: "group", disabled: false },
],
handleValue: async (value) => {
const views = { group: GroupMain } as Record<string, CliView>;
if (Object.keys(views).includes(value)) return await views[value].show();
},
});
13 changes: 13 additions & 0 deletions cli/ui/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export function clear(messages?: string | string[]) {
console.log("\x1Bc");

if (messages) {
if (typeof messages === "string") {
messages = [messages];
}

messages.forEach((msg) => {
console.error(msg);
});
}
}

0 comments on commit d4e0a78

Please sign in to comment.