Skip to content

Commit

Permalink
create route to upload battery files and list stages on admin page
Browse files Browse the repository at this point in the history
  • Loading branch information
ross3102 committed Nov 28, 2023
1 parent ef3a0a2 commit 7d78f21
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 7 deletions.
4 changes: 2 additions & 2 deletions packages/api/src/models/battery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const optionSchema = new Schema<IOption>(

const batteryStageSchema = new Schema<IBatteryStage>({
type: { type: String, required: true },
stageLabel: { type: String, required: true },
stageLabel: String,
options: [optionSchema],
});

Expand Down Expand Up @@ -102,7 +102,7 @@ export const BatteryStage = model<IBatteryStage>(

export const batterySchema = new Schema<IBattery>({
name: { type: String, required: true },
description: { type: String, required: true },
description: String,
imageUrl: { type: String, required: true },
stages: [{ type: Schema.Types.ObjectId, ref: "BatteryStage" }],
});
Expand Down
80 changes: 78 additions & 2 deletions packages/api/src/routes/admin.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,86 @@
import { Router } from "express";
import isAdmin from "../middleware/admin";
import {
Battery,
BatteryStage,
IBattery,
IBatteryStage,
IOption,
} from "../models/battery";
import { HydratedDocument } from "mongoose";

const router = Router();

router.get("/", isAdmin, (req, res) => {
res.status(200).send("You are an admin");
router.get("/stages", isAdmin, (req, res, next) => {
BatteryStage.find()
.then((stages) => res.json(stages))
.catch(next);
});

/* eslint-disable @typescript-eslint/no-explicit-any */
router.post("/battery", isAdmin, async (req, res, next) => {
try {
const json = req.body as Record<string, any> & {
Stages: Record<string, any>[];
};
const name = json["Name"];
const desc = json["Description"];
const imageUrl = `https://picsum.photos/300/300?${crypto.randomUUID()}`;
const stages: IBatteryStage[] = json["Stages"].map((s: any) => {
const options: IOption[] = Object.entries(s).reduce(
(acc: IOption[], item: any) => {
const optionName = item[0];
const optionValue = item[1];
if (
["Type", "StageLabel"].includes(optionName) ||
typeof optionValue === "object"
)
return acc;
const option: IOption = {
name: optionName,
default: optionValue,
type:
typeof optionValue === "number"
? "number"
: typeof optionValue === "boolean"
? "checkbox"
: "text",
};
acc.push(option);
return acc;
},
[]
);

return {
stageLabel: s["StageLabel"],
type: s["Type"],
options,
};
});

const newStages: HydratedDocument<IBatteryStage>[] = [];

for (const stage of stages) {
const existing = await BatteryStage.findOne({ type: stage.type });
if (!existing) {
newStages.push(await BatteryStage.create(stage));
}
}

const bat: IBattery = {
name: name,
description: desc,
imageUrl: imageUrl,
stages: newStages.map((s) => s._id),
};

const data = await Battery.create(bat);
res.status(201).json(data);
} catch (e) {
next(e);
}
});
/* eslint-enable @typescript-eslint/no-explicit-any */

export default router;
48 changes: 48 additions & 0 deletions packages/ui/src/api/admin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import axios from "axios";

const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_URL,
withCredentials: true,
});

interface IGenericOption<T> {
name: string;
default: T;
}

interface INumberOption extends IGenericOption<number> {
type: "number";
min?: number;
max?: number;
step?: number;
}

interface ITextOption extends IGenericOption<string> {
type: "text";
maxLength?: number;
}

interface IDropdownOption extends IGenericOption<number> {
type: "dropdown";
options: string[];
}

interface ICheckboxOption extends IGenericOption<boolean> {
type: "checkbox";
}

type IOption = INumberOption | ITextOption | IDropdownOption | ICheckboxOption;

interface GetStageResponse {
_id: string;
type: string;
stageLabel: string;
options: IOption[];
}

async function getStages() {
const result = await axiosInstance.get<GetStageResponse[]>("admin/stages");
return result.data;
}

export default { getStages };
2 changes: 1 addition & 1 deletion packages/ui/src/components/AppNavbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function logOut() {
<ElMenuItem index="/dashboard/new">Dashboard</ElMenuItem>
<div class="flex-1"></div>
<template v-if="authStore.currentUser.isAdmin">
<ElMenuItem index="admin">Admin</ElMenuItem>
<ElMenuItem index="/admin">Admin</ElMenuItem>
</template>
<ElMenuItem index="/logout" :onclick="logOut">
Log Out, {{ authStore.currentUser.email }}
Expand Down
17 changes: 15 additions & 2 deletions packages/ui/src/pages/AdminPage.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
<script setup lang="ts">
import { useRouter } from "vue-router";
import { useAuthStore } from "@/stores/auth";
import { useQuery } from "@tanstack/vue-query";
import adminAPI from "@/api/admin";
const router = useRouter();
const authStore = useAuthStore();
if (!authStore.currentUser?.isAdmin) {
router.push("/");
}
const { data } = useQuery({
queryKey: ["admin", "stages"],
queryFn: adminAPI.getStages,
});
</script>
<template>
<h1>ADMIN PAGE</h1>
hi you are an admin :D
<h1 class="text-2xl">Stages</h1>
<div v-for="stage in data" :key="stage._id" class="border border-black p-4">
<div class="text-xl">{{ stage.type }} - {{ stage.stageLabel }}</div>
<div v-for="option in stage.options" :key="option.name">
<span class="font-bold">{{ option.name }} ({{ option.type }}):</span>
{{ option.default }}
</div>
</div>
</template>

0 comments on commit 7d78f21

Please sign in to comment.