diff --git a/functions/package-lock.json b/functions/package-lock.json
index b5d8660..1c2dd54 100644
--- a/functions/package-lock.json
+++ b/functions/package-lock.json
@@ -6,8 +6,9 @@
     "": {
       "name": "functions",
       "dependencies": {
+        "cors": "^2.8.5",
         "firebase-admin": "^10.0.2",
-        "firebase-functions": "^3.18.0"
+        "firebase-functions": "^4.0.1"
       },
       "devDependencies": {
         "@typescript-eslint/eslint-plugin": "^5.12.0",
@@ -1972,25 +1973,24 @@
       }
     },
     "node_modules/firebase-functions": {
-      "version": "3.24.1",
-      "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.24.1.tgz",
-      "integrity": "sha512-GYhoyOV0864HFMU1h/JNBXYNmDk2MlbvU7VO/5qliHX6u/6vhSjTJjlyCG4leDEI8ew8IvmkIC5QquQ1U8hAuA==",
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.0.1.tgz",
+      "integrity": "sha512-U0dOqGPShLi0g3jUlZ3aZlVTPFO9cREJfIxMJIlfRz/vNbYoKdIVdI7OAS9RKPcqz99zxkN/A8Ro4kjI+ytT8A==",
       "dependencies": {
         "@types/cors": "^2.8.5",
         "@types/express": "4.17.3",
         "cors": "^2.8.5",
         "express": "^4.17.1",
-        "lodash": "^4.17.14",
         "node-fetch": "^2.6.7"
       },
       "bin": {
         "firebase-functions": "lib/bin/firebase-functions.js"
       },
       "engines": {
-        "node": "^8.13.0 || >=10.10.0"
+        "node": ">=14.10.0"
       },
       "peerDependencies": {
-        "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0"
+        "firebase-admin": "^10.0.0 || ^11.0.0"
       }
     },
     "node_modules/firebase-functions-test": {
@@ -2952,7 +2952,8 @@
     "node_modules/lodash": {
       "version": "4.17.21",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
-      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "dev": true
     },
     "node_modules/lodash.camelcase": {
       "version": "4.3.0",
@@ -5960,15 +5961,14 @@
       }
     },
     "firebase-functions": {
-      "version": "3.24.1",
-      "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.24.1.tgz",
-      "integrity": "sha512-GYhoyOV0864HFMU1h/JNBXYNmDk2MlbvU7VO/5qliHX6u/6vhSjTJjlyCG4leDEI8ew8IvmkIC5QquQ1U8hAuA==",
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.0.1.tgz",
+      "integrity": "sha512-U0dOqGPShLi0g3jUlZ3aZlVTPFO9cREJfIxMJIlfRz/vNbYoKdIVdI7OAS9RKPcqz99zxkN/A8Ro4kjI+ytT8A==",
       "requires": {
         "@types/cors": "^2.8.5",
         "@types/express": "4.17.3",
         "cors": "^2.8.5",
         "express": "^4.17.1",
-        "lodash": "^4.17.14",
         "node-fetch": "^2.6.7"
       }
     },
@@ -6687,7 +6687,8 @@
     "lodash": {
       "version": "4.17.21",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
-      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "dev": true
     },
     "lodash.camelcase": {
       "version": "4.3.0",
diff --git a/functions/package.json b/functions/package.json
index a85202b..d17b86b 100644
--- a/functions/package.json
+++ b/functions/package.json
@@ -15,8 +15,9 @@
   },
   "main": "lib/index.js",
   "dependencies": {
+    "cors": "^2.8.5",
     "firebase-admin": "^10.0.2",
-    "firebase-functions": "^3.18.0"
+    "firebase-functions": "^4.0.1"
   },
   "devDependencies": {
     "@typescript-eslint/eslint-plugin": "^5.12.0",
diff --git a/functions/src/index.ts b/functions/src/index.ts
index edb22a1..371a2e2 100644
--- a/functions/src/index.ts
+++ b/functions/src/index.ts
@@ -1,163 +1,10 @@
 import * as functions from "firebase-functions";
 import {initializeApp} from "firebase-admin/app";
-import {
-  DocumentReference,
-  DocumentSnapshot,
-  getFirestore,
-  QuerySnapshot,
-} from "firebase-admin/firestore";
-import express, {Request, Response} from "express";
-
-type VoteEntry = {
-  username: string
-  voteIndex: number
-}
-
-type Vote = {
-  prompt: string
-  options: Array<string>
-}
 
 initializeApp();
 
+import votingRoute from "./route/voting";
+import adminRoute from "./route/admin";
 
-const db = getFirestore();
-const app = express();
-
-const getVote = async (id: string) =>
-  db.collection("votes")
-      .doc(id)
-      .get() as Promise<DocumentSnapshot<Vote>>;
-const getVoteEntry = async (vote: DocumentReference<Vote>, username: string) =>
-  vote.collection("entries")
-      .where("username", "==", username)
-      .get() as Promise<QuerySnapshot<VoteEntry>>;
-const makeVoteEntry = async (
-    vote: DocumentReference<Vote>,
-    username: string,
-    voteIndex: number
-) =>
-  vote.collection("entries")
-      .add({username, voteIndex}) as Promise<DocumentReference<VoteEntry>>;
-
-app.post("/", async (req: Request, res: Response) => {
-  const voteId = req.body.voteId as string | undefined;
-  const voter = req.body.voter as string | undefined;
-  const voteIndex = parseInt((req.body.voteIndex as string | undefined) ?? "");
-
-  if (!voteId) {
-    res.status(400).json({error: "Missing voteId"});
-    return;
-  }
-
-  if (!voter) {
-    res.status(400).json({error: "Missing voter"});
-    return;
-  }
-
-  const vote = await getVote(voteId);
-  if (!vote.exists) {
-    res.status(404).json({error: "Vote not found"});
-    return;
-  }
-
-  if (
-    Number.isNaN(voteIndex) ||
-    voteIndex < 0 ||
-    voteIndex >= (vote.data()?.options ?? []).length
-  ) {
-    res.status(400).json({error: "Invalid vote index"});
-    return;
-  }
-
-  const entry = await getVoteEntry(vote.ref, voter);
-  if (entry.empty) {
-    await makeVoteEntry(vote.ref, voter, voteIndex);
-    res.json({success: true});
-    return;
-  }
-
-  entry.docs[0].ref.update({voteIndex});
-  res.json({success: true});
-});
-
-app.get("/", async (req: Request, res: Response) => {
-  const voteId = req.query.voteId as string | undefined;
-  if (!voteId) {
-    res.status(400).json({error: "Missing voteId"});
-    return;
-  }
-
-  const vote = await getVote(voteId);
-  if (!vote.exists) {
-    res.status(404).json({error: "Vote not found"});
-    return;
-  }
-
-  res.json(vote.data());
-});
-
-app.get("/entries", async (req: Request, res: Response) => {
-  const voteId = req.query.voteId as string | undefined;
-  const voteIndexStr = req.query.voteIndex as string | undefined;
-  const voteIndex = parseInt(voteIndexStr ?? "");
-  if (!voteId) {
-    res.status(400).json({error: "Missing voteId"});
-    return;
-  }
-
-  if (Number.isNaN(voteIndex) && voteIndexStr) {
-    res.status(400).json({error: "Invalid voteIndex"});
-    return;
-  }
-
-  const vote = await getVote(voteId);
-  if (!vote.exists) {
-    res.status(404).json({error: "Vote not found"});
-    return;
-  }
-
-  if (
-    !Number.isNaN(voteIndex) &&
-    (voteIndex < 0 || voteIndex >= (vote.data()?.options ?? []).length)
-  ) {
-    res.status(400).json({error: "Invalid vote index"});
-    return;
-  }
-
-  const entryCollection = vote.ref.collection("entries");
-  const entries = await (
-    Number.isNaN(voteIndex) ?
-      entryCollection :
-      entryCollection.where("voteIndex", "==", voteIndex)
-  ).get();
-  res.json(entries.docs.map((d) => d.data()));
-});
-
-app.get("/count", async (req: Request, res: Response) => {
-  const voteId = req.query.voteId as string | undefined;
-
-  if (!voteId) {
-    res.status(400).json({error: "Missing voteId"});
-    return;
-  }
-
-  const vote = await getVote(voteId);
-  if (!vote.exists) {
-    res.status(404).json({error: "Vote not found"});
-    return;
-  }
-
-  const collect = new Array<Promise<QuerySnapshot<VoteEntry>>>();
-  for (let i = 0; i < (vote.data()?.options ?? []).length; i++) {
-    collect.push(vote.ref
-        .collection("entries")
-        .where("voteIndex", "==", i)
-        .get() as Promise<QuerySnapshot<VoteEntry>>
-    );
-  }
-
-  res.json((await Promise.all(collect)).map((query) => query.size));
-});
-
-export const vote = functions.https.onRequest(app);
+export const vote = functions.https.onRequest(votingRoute);
+export const admin = functions.https.onRequest(adminRoute);
diff --git a/functions/src/route/admin.ts b/functions/src/route/admin.ts
new file mode 100644
index 0000000..6bc0f72
--- /dev/null
+++ b/functions/src/route/admin.ts
@@ -0,0 +1,48 @@
+import express, {Request, Response} from "express";
+import cors from "cors";
+import {createVote, setActiveVote} from "../types/vote";
+
+const app = express();
+
+app.use(cors());
+
+app.post("/create", async (req: Request, res: Response) => {
+  const prompt = req.body.prompt as string | undefined;
+  const options = req.body.options as Array<string> | undefined;
+
+  if (!prompt) {
+    res.status(400).json({error: "Missing prompt"});
+    return;
+  }
+
+  if (!options || options.length < 2) {
+    res.status(400).json({error: "Missing options"});
+    return;
+  }
+
+  res.json({id: await createVote(prompt, options)});
+});
+
+app.put("/setActive", async (req: Request, res: Response) => {
+  const voteId = req.body.voteId as string | undefined;
+
+  if (!voteId) {
+    res.status(400).json({error: "Missing voteId"});
+    return;
+  }
+
+  res.json({success: await setActiveVote(voteId)});
+});
+
+app.put("/closeVote", async (req: Request, res: Response) => {
+  const voteId = req.body.voteId as string | undefined;
+
+  if (!voteId) {
+    res.status(400).json({error: "Missing voteId"});
+    return;
+  }
+
+  res.json({success: await setActiveVote(voteId, false)});
+});
+
+export default app;
diff --git a/functions/src/route/voting.ts b/functions/src/route/voting.ts
new file mode 100644
index 0000000..2437193
--- /dev/null
+++ b/functions/src/route/voting.ts
@@ -0,0 +1,125 @@
+import express, {Request, Response} from "express";
+import {
+  getEntriesFromSnapshot,
+  getVoteSnapshot,
+  getVoteCounts,
+  getVoteEntry,
+  makeVoteEntry,
+  updateVoteEntry,
+  getVote,
+  getActiveVote,
+} from "../types/vote";
+
+
+const app = express();
+
+app.post("/", async (req: Request, res: Response) => {
+  const voteId = req.body.voteId as string | undefined;
+  const voter = req.body.voter as string | undefined;
+  const voteIndex = parseInt((req.body.voteIndex as string | undefined) ?? "");
+
+  if (!voteId) {
+    res.status(400).json({error: "Missing voteId"});
+    return;
+  }
+
+  if (!voter) {
+    res.status(400).json({error: "Missing voter"});
+    return;
+  }
+
+  const vote = await getVoteSnapshot(voteId);
+  if (!vote.exists) {
+    res.status(404).json({error: "Vote not found"});
+    return;
+  }
+
+  if (
+    Number.isNaN(voteIndex) ||
+    voteIndex < 0 ||
+    voteIndex >= (vote.data()?.options ?? []).length
+  ) {
+    res.status(400).json({error: "Invalid vote index"});
+    return;
+  }
+
+  const entry = await getVoteEntry(vote.ref, voter);
+  if (!entry) {
+    await makeVoteEntry(vote.ref, voter, voteIndex);
+    res.json({success: true});
+    return;
+  }
+
+  await updateVoteEntry(entry, voteIndex);
+  res.json({success: true});
+});
+
+app.get("/", async (req: Request, res: Response) => {
+  const voteId = req.query.voteId as string | undefined;
+  if (!voteId) {
+    res.status(400).json({error: "Missing voteId"});
+    return;
+  }
+
+  const vote = await getVote(voteId);
+  if (!vote) {
+    res.status(404).json({error: "Vote not found"});
+    return;
+  }
+
+  res.json(vote);
+});
+
+app.get("/entries", async (req: Request, res: Response) => {
+  const voteId = req.query.voteId as string | undefined;
+  const voteIndexStr = req.query.voteIndex as string | undefined;
+  const voteIndex = parseInt(voteIndexStr ?? "");
+  if (!voteId) {
+    res.status(400).json({error: "Missing voteId"});
+    return;
+  }
+
+  if (Number.isNaN(voteIndex) && voteIndexStr) {
+    res.status(400).json({error: "Invalid voteIndex"});
+    return;
+  }
+
+  const vote = await getVoteSnapshot(voteId);
+  if (!vote.exists) {
+    res.status(404).json({error: "Vote not found"});
+    return;
+  }
+
+  if (
+    !Number.isNaN(voteIndex) &&
+    (voteIndex < 0 || voteIndex >= (vote.data()?.options ?? []).length)
+  ) {
+    res.status(400).json({error: "Invalid vote index"});
+    return;
+  }
+
+  res.json(await getEntriesFromSnapshot(vote, voteIndex));
+});
+
+app.get("/count", async (req: Request, res: Response) => {
+  const voteId = req.query.voteId as string | undefined;
+
+  if (!voteId) {
+    res.status(400).json({error: "Missing voteId"});
+    return;
+  }
+
+  const vote = await getVoteSnapshot(voteId);
+  if (!vote.exists) {
+    res.status(404).json({error: "Vote not found"});
+    return;
+  }
+
+  res.json((await Promise.all(getVoteCounts(vote))).map((query) => query.size));
+});
+
+app.get("/active", async (req: Request, res: Response) => {
+  res.json({id: (await getActiveVote())?.id});
+});
+
+export default app;
diff --git a/functions/src/types/vote.ts b/functions/src/types/vote.ts
new file mode 100644
index 0000000..1827003
--- /dev/null
+++ b/functions/src/types/vote.ts
@@ -0,0 +1,104 @@
+import {
+  DocumentReference,
+  DocumentSnapshot,
+  getFirestore,
+  QuerySnapshot,
+} from "firebase-admin/firestore";
+
+const db = getFirestore();
+const votesCollection = db.collection("votes");
+const entriesCollectionName = "entries";
+const usernameField = "username";
+const voteIndexField = "voteIndex";
+const optionsField = "options";
+const activeField = "active";
+
+export type VoteEntry = {
+    [usernameField]: string
+    [voteIndexField]: number
+};
+
+export type Vote = {
+    prompt: string
+    [optionsField]: Array<string>
+    [activeField]?: boolean
+};
+
+export const getEntriesCollection = (vote: DocumentReference<Vote>) =>
+  vote.collection(entriesCollectionName);
+
+
+export const getVoteSnapshot = async (id: string) =>
+    votesCollection
+        .doc(id)
+        .get() as Promise<DocumentSnapshot<Vote>>;
+export const getVote = async (id: string) => (await getVoteSnapshot(id)).data();
+export const getVoteEntry = async (
+    vote: DocumentReference<Vote>,
+    username: string
+) =>
+  (await getEntriesCollection(vote)
+      .where(usernameField, "==", username)
+      .get() as QuerySnapshot<VoteEntry>).docs[0]?.ref;
+export const updateVoteEntry = async (
+    vote: DocumentReference<VoteEntry>,
+    voteIndex: number
+) =>
+  vote.update({voteIndex});
+export const makeVoteEntry = async (
+    vote: DocumentReference<Vote>,
+    username: string,
+    voteIndex: number
+) =>
+  vote.collection(entriesCollectionName)
+      .add({username, voteIndex}) as Promise<DocumentReference<VoteEntry>>;
+
+export const getVoteCounts = (
+    vote: DocumentSnapshot<Vote>
+): Array<Promise<QuerySnapshot<VoteEntry>>> => {
+  const collect = new Array<Promise<QuerySnapshot<VoteEntry>>>();
+  for (let i = 0; i < (vote.data()?.options ?? []).length; i++) {
+    collect.push(getEntriesCollection(vote.ref)
+        .where(voteIndexField, "==", i)
+        .get() as Promise<QuerySnapshot<VoteEntry>>
+    );
+  }
+
+  return collect;
+};
+
+export const getEntriesFromSnapshot = async (
+    snapshot: DocumentSnapshot<Vote>,
+    voteIndex: number
+): Promise<VoteEntry[]> => {
+  const entryCollection = getEntriesCollection(snapshot.ref);
+  return (await (
+        Number.isNaN(voteIndex) ?
+            entryCollection :
+            entryCollection.where(voteIndexField, "==", voteIndex)
+  ).get()
+  ).docs.map((doc) => doc.data() as VoteEntry);
+};
+
+export const createVote = async (prompt: string, options: Array<string>) => {
+  const vote = await votesCollection.add({prompt, options});
+  return vote.id;
+};
+
+export const getActiveVote = async () =>
+    (await votesCollection.where(activeField, "==", true)
+        .get()).docs[0]?.ref as DocumentReference<Vote> | undefined;
+export const setActiveVote = async (id: string, active = true) => {
+  const activeVote = await getActiveVote();
+  if (activeVote) {
+    await activeVote.update({active: false});
+  }
+
+  const vote = await getVoteSnapshot(id);
+  if (!vote.exists) {
+    return false;
+  }
+
+  await vote.ref.update({active});
+  return true;
+};