diff --git a/assets/global.css b/assets/global.css
index 95d32af..e74cf7a 100644
--- a/assets/global.css
+++ b/assets/global.css
@@ -60,6 +60,7 @@ button {
fieldset {
padding-inline: 0.5rem;
+ width: fit-content;
}
label {
@@ -70,7 +71,7 @@ label>* {
margin-inline-start: 0.5rem;
}
-p + p {
+:is(p, form, fieldset, pre) + :is(p, form, fieldset, pre) {
margin-block-start: 0.5rem;
}
diff --git a/components/Header.vue b/components/Header.vue
index b4cb0d4..2c6a2c1 100644
--- a/components/Header.vue
+++ b/components/Header.vue
@@ -8,11 +8,11 @@
- {{ session.account.name || "anonymous" }}
+ {{ session.account.name }}
(s:{{ session.id }} a:{{ session.account.id }}{{ session.push ? " push" : null }})
{{ session.account.type }}
Settings
-
+
Log In
diff --git a/pages/account/settings.vue b/pages/account/settings.vue
index aba7fb6..e1283d4 100644
--- a/pages/account/settings.vue
+++ b/pages/account/settings.vue
@@ -1,7 +1,9 @@
Account Settings
- Name: {{ session?.account.name }}
+
+ Name: {{ session?.account.name }}
+
Access: {{ session?.account.type }}
@@ -37,9 +39,3 @@ async function deleteAccount() {
}
}
-
-
diff --git a/pages/login.vue b/pages/login.vue
index 106a33a..db2a710 100644
--- a/pages/login.vue
+++ b/pages/login.vue
@@ -5,6 +5,25 @@
+
Create Account
+ If you don't have an account you may create one
+
+
+ If you do not wish to deal with logins you may create an anonymous account tied to this device.
+
+
{{ result }}
Session: {{ session }}
@@ -36,4 +55,41 @@ async function logIn() {
result.value = `Server replied: ${err.statusCode} ${err.statusMessage}`;
}
}
+
+const createName = ref("");
+async function createAccount() {
+ try {
+ const res = await $fetch.raw("/api/account", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: new URLSearchParams({ name: createName.value })
+ });
+ result.value = `Server replied: ${res.status} ${res.statusText}`;
+ await sessionRefresh();
+
+ } catch (err: any) {
+ console.log(err);
+ console.log(err.data);
+ result.value = `Server replied: ${err.statusCode} ${err.statusMessage}`;
+ }
+}
+async function createAnonymousAccount() {
+ try {
+ const res = await $fetch.raw("/api/account", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ });
+ result.value = `Server replied: ${res.status} ${res.statusText}`;
+ await sessionRefresh();
+
+ } catch (err: any) {
+ console.log(err);
+ console.log(err.data);
+ result.value = `Server replied: ${err.statusCode} ${err.statusMessage}`;
+ }
+}
diff --git a/pages/schedule.vue b/pages/schedule.vue
index 5911103..f161c34 100644
--- a/pages/schedule.vue
+++ b/pages/schedule.vue
@@ -5,9 +5,10 @@
Study carefully, we only hold these events once a year.
- Login to get notified about updates to the schedule.
+ Login or Create an account
+ to get notified about updates to the schedule.
-
+
Check out your Account Setting to set up notifications for changes to schedule.
Schedule
diff --git a/server/api/account.post.ts b/server/api/account.post.ts
new file mode 100644
index 0000000..11b65cc
--- /dev/null
+++ b/server/api/account.post.ts
@@ -0,0 +1,53 @@
+import { readAccounts, writeAccounts, nextAccountId } from "~/server/database";
+import { Account } from "~/shared/types/account";
+
+export default defineEventHandler(async (event) => {
+ let session = await getAccountSession(event);
+ if (session) {
+ throw createError({
+ status: 409,
+ message: "Cannot create account while having an active session."
+ });
+ }
+
+ const formData = await readFormData(event);
+ const name = formData.get("name");
+
+ const accounts = await readAccounts();
+ let account: Account;
+ if (typeof name === "string") {
+ if (name === "") {
+ throw createError({
+ status: 400,
+ message: "Name cannot be blank",
+ });
+ }
+ if (accounts.some(account => account.name && account.name.toLowerCase() === name.toLowerCase())) {
+ throw createError({
+ status: 409,
+ message: "User already exists",
+ });
+ }
+
+ account = {
+ id: await nextAccountId(),
+ type: "regular",
+ name,
+ };
+
+ } else if (name === null) {
+ account = {
+ id: await nextAccountId(),
+ type: "anonymous",
+ };
+ } else {
+ throw createError({
+ status: 400,
+ message: "Invalid name",
+ });
+ }
+
+ accounts.push(account);
+ await writeAccounts(accounts);
+ await setAccountSession(event, account.id);
+})
diff --git a/server/api/auth/session.delete.ts b/server/api/auth/session.delete.ts
index c3fbf4e..565745c 100644
--- a/server/api/auth/session.delete.ts
+++ b/server/api/auth/session.delete.ts
@@ -1,3 +1,19 @@
+import { readAccounts } from "~/server/database";
+
export default defineEventHandler(async (event) => {
+ const session = await getAccountSession(event);
+ if (session) {
+ const accounts = await readAccounts();
+ const account = accounts.find(
+ account => account.id === session.accountId
+ );
+ if (account && account.type === "anonymous") {
+ throw createError({
+ status: 409,
+ message: "Cannot log out of an anonymous account",
+ });
+ }
+ }
+
await clearAccountSession(event);
})
diff --git a/server/database.ts b/server/database.ts
index 16e65eb..e98379a 100644
--- a/server/database.ts
+++ b/server/database.ts
@@ -9,6 +9,7 @@ import { generateDemoSchedule, generateDemoAccounts } from "./generate-demo-sche
const schedulePath = "data/schedule.json";
const subscriptionsPath = "data/subscriptions.json";
const accountsPath = "data/accounts.json";
+const nextAccountIdPath = "data/next-account-id.json";
const sessionsPath = "data/sessions.json";
const nextSessionIdPath = "data/next-session-id.json";
@@ -45,6 +46,15 @@ export async function writeSubscriptions(subscriptions: Subscription[]) {
await writeFile(subscriptionsPath, JSON.stringify(subscriptions, undefined, "\t") + "\n", "utf-8");
}
+export async function nextAccountId() {
+ let nextId = await readJson(nextAccountIdPath, 0);
+ if (nextId === 0) {
+ nextId = Math.max(...(await readAccounts()).map(account => account.id), -1) + 1;
+ }
+ await writeFile(nextAccountIdPath, String(nextId + 1), "utf-8");
+ return nextId;
+}
+
export async function readAccounts() {
return await readJson(accountsPath, generateDemoAccounts);
}