commit ea1438363b9206f347efe219fc3785c8965376cc
Author: Korbs
Date: Tue Jul 16 02:05:26 2024 -0400
Init
diff --git a/.env.sample b/.env.sample
new file mode 100644
index 0000000..5df4df0
--- /dev/null
+++ b/.env.sample
@@ -0,0 +1,3 @@
+PUBLIC_APPWRITE_ENDPOINT="https://example.com/v1"
+PUBLIC_APPWRITE_PROJECT_ID=""
+APPWRITE_KEY=""
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ffe304a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,20 @@
+# build output
+dist/
+
+# generated types
+.astro/
+
+# dependencies
+node_modules/
+
+# logs
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# environment variables
+.env
+
+# macOS-specific files
+.DS_Store
diff --git a/astro.config.mjs b/astro.config.mjs
new file mode 100644
index 0000000..fe6b0d1
--- /dev/null
+++ b/astro.config.mjs
@@ -0,0 +1,12 @@
+import { defineConfig } from "astro/config";
+import node from "@astrojs/node";
+
+export default defineConfig({
+ output: "server",
+ adapter: node({
+ mode: "standalone",
+ }),
+ security: {
+ checkOrigin: true
+ }
+});
diff --git a/bun.lockb b/bun.lockb
new file mode 100755
index 0000000..14de6bf
Binary files /dev/null and b/bun.lockb differ
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..b0622b1
--- /dev/null
+++ b/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "server-side-rendering",
+ "type": "module",
+ "version": "0.0.1",
+ "scripts": {
+ "dev": "astro dev",
+ "start": "astro dev",
+ "build": "astro check && astro build",
+ "preview": "astro preview",
+ "astro": "astro"
+ },
+ "dependencies": {
+ "@appwrite.io/pink": "^0.3.0",
+ "@appwrite.io/pink-icons": "^0.3.0",
+ "@astrojs/check": "^0.4.1",
+ "@astrojs/node": "^8.1.0",
+ "appwrite": "^15.0.0",
+ "astro": "^4.11.5",
+ "node-appwrite": "^12.0.0",
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/src/env.d.ts b/src/env.d.ts
new file mode 100644
index 0000000..f52e718
--- /dev/null
+++ b/src/env.d.ts
@@ -0,0 +1,18 @@
+///
+
+declare namespace App {
+ interface Locals {
+ user?: import("node-appwrite").Models.User >;
+ users?: import("node-appwrite").Service.UserList >;
+ }
+}
+
+interface ImportMetaEnv {
+ readonly PUBLIC_APPWRITE_ENDPOINT: string;
+ readonly PUBLIC_APPWRITE_PROJECT_ID: string;
+ readonly APPWRITE_KEY: string;
+}
+
+interface ImportMeta {
+ readonly env: ImportMetaEnv;
+}
\ No newline at end of file
diff --git a/src/images/appwrite-logo-dark.svg b/src/images/appwrite-logo-dark.svg
new file mode 100644
index 0000000..af9916d
--- /dev/null
+++ b/src/images/appwrite-logo-dark.svg
@@ -0,0 +1,13 @@
+
diff --git a/src/images/login-dark-mode.png b/src/images/login-dark-mode.png
new file mode 100644
index 0000000..682b517
Binary files /dev/null and b/src/images/login-dark-mode.png differ
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro
new file mode 100644
index 0000000..4fa864c
--- /dev/null
+++ b/src/layouts/Layout.astro
@@ -0,0 +1 @@
+
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000..0fdff14
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,11 @@
+import { defineMiddleware } from "astro:middleware";
+import { createSessionClient } from "./server/appwrite";
+
+export const onRequest = defineMiddleware(async ({ request, locals }, next) => {
+ try {
+ const { account } = createSessionClient(request);
+ locals.user = await account.get();
+ } catch {}
+
+ return next();
+});
diff --git a/src/oauth.ts b/src/oauth.ts
new file mode 100644
index 0000000..433d85d
--- /dev/null
+++ b/src/oauth.ts
@@ -0,0 +1,16 @@
+export async function signInWithDiscord() {
+ const url = new URL(
+ `${import.meta.env.PUBLIC_APPWRITE_ENDPOINT}/account/sessions/oauth2/discord`
+ );
+
+ const origin = window.location.origin;
+
+ url.searchParams.set("project", import.meta.env.PUBLIC_APPWRITE_PROJECT_ID!);
+ url.searchParams.set("success", `${origin}/account`);
+ url.searchParams.set("failure", `${origin}/signin`);
+
+ /* Important: For SSR we set token=true to get a auth token in the success URL */
+ url.searchParams.set("token", `true`);
+
+ window.location.href = url.toString();
+}
diff --git a/src/pages/account.astro b/src/pages/account.astro
new file mode 100644
index 0000000..165f316
--- /dev/null
+++ b/src/pages/account.astro
@@ -0,0 +1,45 @@
+---
+const { user, users } = Astro.locals
+if (!user) {return Astro.redirect("/signin")}
+import { SESSION_COOKIE, createAdminClient } from "../server/appwrite";
+
+if (user.emailVerification) {var EmailIsVerified = true}
+else {var EmailIsVerified = false}
+
+if (Astro.cookies.get('anonymous')) {var AN = true}
+else {var AN = false}
+
+const { account } = createAdminClient();
+// const session = await account.createMagicURLToken(user.$id,user.email);
+---
+
+Email: {AN ? None : {user.email}}
+ID: {user.$id}
+
+Name: {AN ? Anonymous : {user.name}}
+Created at: {user.$createdAt}
+Last login: {user.accessedAt}
+Last updated: {user.$updatedAt}
+Labels:
+ {user.labels.map((data) =>
+
+ )}
+
+
+Theme: {user.prefs.darkTheme}
+
+{EmailIsVerified ?
+ Your account is verified
+ :
+ Your account is not verified
+}
+
+{AN ?
+ This is an anonmyous account.
+ :
+ Not an anonmyous account.
+}
+
+Sign Out
\ No newline at end of file
diff --git a/src/pages/api/an.ts b/src/pages/api/an.ts
new file mode 100644
index 0000000..cc22f94
--- /dev/null
+++ b/src/pages/api/an.ts
@@ -0,0 +1,24 @@
+import type { APIRoute } from "astro";
+import { createAdminClient, SESSION_COOKIE } from "../../server/appwrite";
+
+export const GET: APIRoute = async ({ cookies, redirect, url }) => {
+ const { account } = createAdminClient();
+
+ cookies.set('anonymous', 'true')
+ const secret = url.searchParams.get("secret");
+
+ const session = await account.createAnonymousSession(secret);
+ if (!session.secret) {
+ throw new Error("Failed to create session from token");
+ }
+
+ cookies.set(SESSION_COOKIE, session.secret, {
+ sameSite: "strict",
+ expires: new Date(session.expire),
+ secure: true,
+ httpOnly: true,
+ path: "/",
+ });
+
+ return redirect("/account");
+};
diff --git a/src/pages/api/email/send-verification.ts b/src/pages/api/email/send-verification.ts
new file mode 100644
index 0000000..0189dba
--- /dev/null
+++ b/src/pages/api/email/send-verification.ts
@@ -0,0 +1,15 @@
+// When given all roles to ID that is used, the system says
+// the role "applications" is still a missing scope.
+import type { APIRoute } from "astro";
+import { createAdminClient, SESSION_COOKIE } from "../../../server/appwrite";
+
+export const GET: APIRoute = async ({ cookies, redirect, url }) => {
+ const { account } = createAdminClient();
+
+ const result = await account.createVerification(
+ 'http://localhost:4321/account/'
+ );
+
+ console.log(result);
+ return redirect("/account");
+};
diff --git a/src/pages/api/oauth/discord.ts b/src/pages/api/oauth/discord.ts
new file mode 100644
index 0000000..6ee5ea8
--- /dev/null
+++ b/src/pages/api/oauth/discord.ts
@@ -0,0 +1,40 @@
+import type { APIRoute } from "astro";
+import { createAdminClient, SESSION_COOKIE } from "../../../server/appwrite";
+
+export const POST: APIRoute = async ({ redirect, url }) => {
+ const { account } = createAdminClient();
+
+ const redirectUrl = await account.createOAuth2Token(
+ "discord",
+ `${url.origin}/api/oauth`,
+ `${url.origin}/signin`
+ );
+
+ return redirect(redirectUrl);
+};
+
+export const GET: APIRoute = async ({ cookies, redirect, url }) => {
+ const userId = url.searchParams.get("userId");
+ const secret = url.searchParams.get("secret");
+
+ if (!userId || !secret) {
+ throw new Error("OAuth2 did not provide userId or secret");
+ }
+
+ const { account } = createAdminClient();
+
+ const session = await account.createSession(userId, secret);
+ if (!session || !session.secret) {
+ throw new Error("Failed to create session from token");
+ }
+
+ cookies.set(SESSION_COOKIE, session.secret, {
+ sameSite: "strict",
+ expires: new Date(session.expire),
+ secure: true,
+ httpOnly: true,
+ path: "/",
+ });
+
+ return redirect("/account");
+};
diff --git a/src/pages/index.astro b/src/pages/index.astro
new file mode 100644
index 0000000..e068fca
--- /dev/null
+++ b/src/pages/index.astro
@@ -0,0 +1,8 @@
+---
+const { user } = Astro.locals;
+if (user) {
+ return Astro.redirect("/account");
+}
+
+return Astro.redirect("/signin");
+---
diff --git a/src/pages/settings/update-username.astro b/src/pages/settings/update-username.astro
new file mode 100644
index 0000000..a1880de
--- /dev/null
+++ b/src/pages/settings/update-username.astro
@@ -0,0 +1,34 @@
+---
+import { Users } from "node-appwrite";
+import { createAdminClient } from "../../server/appwrite";
+
+if (Astro.request.method === "POST") {
+ const data = await Astro.request.formData();
+ const name = data.get("name") as string;
+
+ const { account } = createAdminClient();
+
+ const promise = await account.updateName(name);
+
+ promise.then(function (response) {
+ console.log(response);
+ }, function (error) {
+ console.log(error);
+ });
+
+
+ return Astro.redirect("/account");
+}
+---
+
+
\ No newline at end of file
diff --git a/src/pages/signin.astro b/src/pages/signin.astro
new file mode 100644
index 0000000..d00c3ac
--- /dev/null
+++ b/src/pages/signin.astro
@@ -0,0 +1,105 @@
+---
+import Layout from "../layouts/Layout.astro";
+import { SESSION_COOKIE, createAdminClient } from "../server/appwrite";
+
+const { user } = Astro.locals;
+if (user) {
+ return Astro.redirect("/account");
+}
+
+if (Astro.request.method === "POST") {
+ const data = await Astro.request.formData();
+
+ const email = data.get("email") as string;
+ const password = data.get("password") as string;
+
+ const { account } = createAdminClient();
+ const session = await account.createEmailPasswordSession(email, password);
+
+ Astro.cookies.set(SESSION_COOKIE, session.secret, {
+ path: "/",
+ expires: new Date(session.expire),
+ sameSite: "strict",
+ secure: true,
+ httpOnly: true,
+ });
+
+ return Astro.redirect("/account");
+}
+---
+
+
+
+
Demo sign in
+
+
+ -
+ Don't have an account? Sign up
+
+
+
+
diff --git a/src/pages/signout.astro b/src/pages/signout.astro
new file mode 100644
index 0000000..3ab2b47
--- /dev/null
+++ b/src/pages/signout.astro
@@ -0,0 +1,6 @@
+---
+Astro.cookies.delete('session-token')
+Astro.cookies.delete('anonymous')
+---
+
+
\ No newline at end of file
diff --git a/src/pages/signup.astro b/src/pages/signup.astro
new file mode 100644
index 0000000..4d2fe7c
--- /dev/null
+++ b/src/pages/signup.astro
@@ -0,0 +1,88 @@
+---
+import { ID } from "node-appwrite";
+import { SESSION_COOKIE, createAdminClient } from "../server/appwrite";
+
+const { user } = Astro.locals;
+if (user) {
+ return Astro.redirect("/account");
+}
+
+if (Astro.request.method === "POST") {
+ const data = await Astro.request.formData();
+
+ const email = data.get("email") as string;
+ const password = data.get("password") as string;
+ const name = data.get("name") as string;
+
+ const { account } = createAdminClient();
+
+ await account.create(ID.unique(), email, password, name);
+ const session = await account.createEmailPasswordSession(email, password);
+ const promise = account.createVerification("http://localhost:4321/verify");
+
+ promise.then(
+ function (response) {
+ console.log('VERI:' + response);
+ },
+ function (error) {
+ console.log(error);
+ },
+ );
+
+ Astro.cookies.set(SESSION_COOKIE, session.secret, {
+ path: "/",
+ expires: new Date(session.expire),
+ sameSite: "strict",
+ secure: true,
+ httpOnly: true,
+ });
+
+ return Astro.redirect("/account");
+}
+---
+
+
+
+
+
+
+Create Anonymous Session
\ No newline at end of file
diff --git a/src/server/appwrite.ts b/src/server/appwrite.ts
new file mode 100644
index 0000000..2f9385c
--- /dev/null
+++ b/src/server/appwrite.ts
@@ -0,0 +1,45 @@
+import { Client, Account } from "node-appwrite";
+
+export const SESSION_COOKIE = "session-token";
+
+export function createAdminClient() {
+ const client = new Client()
+ .setEndpoint(import.meta.env.PUBLIC_APPWRITE_ENDPOINT)
+ .setProject(import.meta.env.PUBLIC_APPWRITE_PROJECT_ID)
+ .setKey(import.meta.env.APPWRITE_KEY);
+
+ return {
+ get account() {
+ return new Account(client);
+ },
+ };
+}
+
+export function createSessionClient(request: Request) {
+ const client = new Client()
+ .setEndpoint(import.meta.env.PUBLIC_APPWRITE_ENDPOINT)
+ .setProject(import.meta.env.PUBLIC_APPWRITE_PROJECT_ID);
+
+ const cookies = parseCookies(request.headers.get("cookie") ?? "");
+ const session = cookies.get(SESSION_COOKIE);
+ if (!session) {
+ throw new Error("No session");
+ }
+
+ client.setSession(session);
+
+ return {
+ get account() {
+ return new Account(client);
+ },
+ };
+}
+
+function parseCookies(cookies: string): Map {
+ const map = new Map();
+ for (const cookie of cookies.split(";")) {
+ const [name, value] = cookie.split("=");
+ map.set(name.trim(), value ?? null);
+ }
+ return map;
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..bcbf8b5
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,3 @@
+{
+ "extends": "astro/tsconfigs/strict"
+}