Skip to content

Commit

Permalink
refactoring api and ui; working on profile form
Browse files Browse the repository at this point in the history
  • Loading branch information
jameshschuler committed May 4, 2023
1 parent bc96599 commit e3e229c
Show file tree
Hide file tree
Showing 12 changed files with 172 additions and 87 deletions.
66 changes: 7 additions & 59 deletions api/index.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,21 @@
import profileController from "@/controllers/profile.controller.ts";
import { searchTrainers } from "@/controllers/trainers.controller.ts";
import { logError } from "@/handlers/commands/createLog.handler.ts";
import { syncTrainerCodes } from "@/handlers/commands/syncTrainerCodes.handler.ts";
import { handleError } from "@/middleware/handleError.ts";
import { logRequest } from "@/middleware/logRequest.ts";
import { SyncTrainersRequest } from "@/types/requests/syncTrainersRequest.ts";
import { validateApiKey } from "@/utils/auth.ts";
import { handleResponse } from "@/utils/common.ts";
import { setupRoutes } from "@/routes.ts";
import { oakCors } from "https://deno.land/x/cors/mod.ts";
import { Application, Router, RouterContext, Status } from "oak";
import { Application, Router } from "oak";

const app = new Application();

app.addEventListener("error", (evt) => {
console.error(evt.error);
});

const router = new Router();
export const router = new Router();

app.use(oakCors());

// Log request information
// TODO: type errors
app.use(logRequest);
app.use(handleError);

router
.get("/api/health", async (ctx: RouterContext<string>) => {
ctx.response.body = {
status: "Healthy",
message: "Welcome to PoGo Trainer Codes",
};
})
.get("/api/profile", profileController.getProfile)
.post("/api/profile", profileController.create)
.get("/api/trainer-codes/search", searchTrainers)
// TODO: move to controller
.post("/api/trainer-codes/sync", async (ctx: RouterContext<string>) => {
try {
const isValidApiKey = await validateApiKey(ctx.request.headers.get("X-API-KEY"));
if (!isValidApiKey) {
ctx.response.status = Status.Unauthorized;
return;
}

const body = ctx.request.body();
const result = (await body.value) as SyncTrainersRequest;
const source = result?.source;

if (!source) {
await logError("Missing source", null, ctx.state.requestId);
ctx.response.body = {
success: false,
message: "Missing source",
};
ctx.response.status = Status.BadRequest;
return;
}

const response = await syncTrainerCodes(source!);

handleResponse(ctx, response);
} catch (e) {
await logError("Unable to sync trainer codes.", e, ctx.state.requestId);
ctx.response.body = {
success: false,
message: "Unable to sync trainer codes.",
};
ctx.response.status = Status.InternalServerError;
}
});

setupRoutes(router);
app.use(router.routes());
app.use(router.allowedMethods());

Expand Down
12 changes: 9 additions & 3 deletions api/src/controllers/profile.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ import { CreateProfileRequest } from "@/types/requests/createProfileRequest.ts";
import { handleResponse } from "@/utils/common.ts";
import { Status } from "oak";
import { RouterContext } from "router";
import { ApiResponse } from "../types/common.ts";
import { ProfileResponse } from "../types/response/createProfileResponse.ts";

export function getProfile(ctx: RouterContext<string>) {
// TODO: need to verify user somehow or get user id and store that?
// TODO: pass access code and verify somehow?
ctx.response.body = {
message: "hello",
};
const response = {
data: {
username: "",
},
success: true,
} as ApiResponse<ProfileResponse>;
handleResponse(ctx, response);
}

export async function create(ctx: RouterContext<string>) {
Expand Down
12 changes: 12 additions & 0 deletions api/src/middleware/handleError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { RouterContext } from "oak";

export async function handleError(ctx: RouterContext<string>, next: () => Promise<void>) {
try {
await next();
} catch (err) {
// TODO: handle various errors?
console.error("ERROR!", err);
ctx.response.status = 500;
ctx.response.body = { msg: err.message };
}
}
56 changes: 56 additions & 0 deletions api/src/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import profileController from "@/controllers/profile.controller.ts";
import { searchTrainers } from "@/controllers/trainers.controller.ts";
import { logError } from "@/handlers/commands/createLog.handler.ts";
import { syncTrainerCodes } from "@/handlers/commands/syncTrainerCodes.handler.ts";
import { SyncTrainersRequest } from "@/types/requests/syncTrainersRequest.ts";
import { validateApiKey } from "@/utils/auth.ts";
import { handleResponse } from "@/utils/common.ts";
import { Router, RouterContext, Status } from "oak";

export function setupRoutes(router: Router<Record<string, any>>) {
router
.get("/api/health", (ctx: RouterContext<string>) => {
ctx.response.body = {
status: "Healthy",
message: "Welcome to PoGo Trainer Codes",
};
})
.get("/api/profile", profileController.getProfile)
.post("/api/profile", profileController.create)
.get("/api/trainer-codes/search", searchTrainers)
// TODO: move to controller
.post("/api/trainer-codes/sync", async (ctx: RouterContext<string>) => {
try {
const isValidApiKey = await validateApiKey(ctx.request.headers.get("X-API-KEY"));
if (!isValidApiKey) {
ctx.response.status = Status.Unauthorized;
return;
}

const body = ctx.request.body();
const result = (await body.value) as SyncTrainersRequest;
const source = result?.source;

if (!source) {
await logError("Missing source", null, ctx.state.requestId);
ctx.response.body = {
success: false,
message: "Missing source",
};
ctx.response.status = Status.BadRequest;
return;
}

const response = await syncTrainerCodes(source!);

handleResponse(ctx, response);
} catch (e) {
await logError("Unable to sync trainer codes.", e, ctx.state.requestId);
ctx.response.body = {
success: false,
message: "Unable to sync trainer codes.",
};
ctx.response.status = Status.InternalServerError;
}
});
}
7 changes: 7 additions & 0 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,11 @@ html {
flex-shrink: 0;
}
}
.clickable {
&:hover {
opacity: 0.8;
cursor: pointer;
}
}
</style>
18 changes: 12 additions & 6 deletions frontend/src/components/Navbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
<header>
<nav class="mt-16 mb-8">
<div class="flex justify-between items-center mt-4">
<router-link to="/" class="text-2xl font-medium">PoGo Trainer Hub</router-link>
<router-link to="/" class="text-2xl font-medium pb-2">PoGo Trainer Hub</router-link>
<div>
<router-link to="/search" class="text-lg font-semibold mr-4">Find Trainers</router-link>
<router-link v-if="authStore.isLoggedIn" to="/profile" class="text-lg font-semibold mr-4"
<router-link to="/search" class="text-lg font-semibold mr-5 pb-2">Find Trainers</router-link>
<router-link v-if="authStore.isLoggedIn" to="/profile" class="text-lg font-semibold mr-5 pb-2"
>Profile</router-link
>
<router-link v-if="!authStore.isLoggedIn" to="/login" class="text-lg font-semibold">Log In</router-link>
<button v-if="authStore.isLoggedIn" @click="authStore.logout" class="text-lg font-semibold">Log Out</button>
<router-link v-if="!authStore.isLoggedIn" to="/login" class="text-lg font-semibold pb-2">Log In</router-link>
<button v-if="authStore.isLoggedIn" @click="authStore.logout" class="text-lg font-semibold pb-2">
Log Out
</button>
</div>
</div>
</nav>
Expand All @@ -19,4 +21,8 @@
import { useAuthStore } from "@/stores/authStore";
const authStore = useAuthStore();
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.router-link-active {
border-bottom: 2px solid black;
}
</style>
15 changes: 5 additions & 10 deletions frontend/src/components/Pages/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,15 @@
<h3 class="text-xl">Blah blah blah something about the app Blah blah blah something about the app</h3>
</div>
<div class="flex justify-end items-center">
<button
id="go-to-search--btn"
<router-link
to="/search"
id="go-to-search--btn clickable"
class="p-4 px-6 border-2 border-black rounded-lg font-semibold dark:text-white dark:bg-black"
>
Find Trainers!
</button>
</router-link>
</div>
</div>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped>
#go-to-search--btn {
&:hover {
opacity: 0.8;
}
}
</style>
<style lang="scss" scoped></style>
17 changes: 15 additions & 2 deletions frontend/src/components/Pages/Profile.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
<template>
<div>Profile</div>
<div class="bg-white border-2 border-black rounded-xl p-12">
<div class="flex justify-between items-center">
<h1 class="text-3xl">Profile</h1>
<button id="save-profile--btn" class="py-2 px-4 border-2 border-black rounded-lg clickable">Save</button>
</div>
<ProfileForm />
</div>
</template>
<script setup lang="ts">
import ProfileForm from "@/components/Profile/ProfileForm.vue";
import { useProfileStore } from "@/stores/profileStore";
import { onMounted } from "vue";
Expand All @@ -11,4 +18,10 @@ onMounted(async () => {
await profileStore.getProfile();
});
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
#save-profile--btn {
&:hover {
background-color: lightgreen;
}
}
</style>
45 changes: 45 additions & 0 deletions frontend/src/components/Profile/ProfileForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<template>
<form class="mt-6 flex">
<section class="pr-4">
<div class="flex flex-col my-4">
<label for="username" class="mb-2 font-semibold">Discord Username</label>
<input
class="p-4 rounded-lg border-2 border-black"
type="text"
id="username"
:disabled="true"
:value="profileStore.profile?.username"
/>
</div>
</section>
<section class="pl-4">
<div class="flex flex-col my-4">
<label for="trainer-name" class="mb-2 font-semibold">Trainer Name</label>
<input class="p-4 rounded-lg border-2 border-black" type="text" id="trainer-name" />
</div>

<div class="flex flex-col my-4">
<label for="trainer-code" class="mb-2 font-semibold">Trainer Code</label>
<input class="p-4 rounded-lg border-2 border-black" type="text" id="trainer-code" :disabled="true" />
</div>
</section>
</form>
</template>
<script setup lang="ts">
import { useProfileStore } from "@/stores/profileStore";
const profileStore = useProfileStore();
</script>
<style lang="scss" scoped>
section {
flex: 1 1 0;
input {
&:disabled {
background-color: #e5e7eb;
opacity: 0.75;
border-color: #9ca3af;
}
}
}
</style>
6 changes: 0 additions & 6 deletions frontend/src/components/SearchForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,4 @@ $search-container-height: 175px;
border-color: #000;
}
}
#search--btn {
&:hover {
opacity: 0.8;
}
}
</style>
4 changes: 3 additions & 1 deletion frontend/src/stores/authStore.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { useRouter } from "vue-router";
import { useProfileStore } from "./profileStore";

export const useAuthStore = defineStore("auth", () => {
const profileStore = useProfileStore();
const isLoggedIn = ref<boolean>(false);
const router = useRouter();

function logout() {
localStorage.clear();

isLoggedIn.value = false;
profileStore.profile = null;

// TODO: if current route requires auth then redirect to home
router.push("/");
}

function validateToken() {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ export interface Trainer {
export interface Profile {
profileId: number;
username: string;
userId: string;
}

0 comments on commit e3e229c

Please sign in to comment.