Merge pull request 'Update prod' (#1) from sitetest into master

Reviewed-on: https://git.calones.xyz/Rowblox/rowblox/pulls/1
This commit is contained in:
I-Have-An-Issue 2022-10-13 19:56:52 -05:00
commit dd31b5bafc
50 changed files with 1176 additions and 229 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ node_modules
.env
.env.*
!.env.example
dev.sh

View File

@ -1,8 +1,20 @@
# rowblox-sitetest
# Rowblox
This is the main repository for Rowblox development. **Please, only make commits to the sitetest branch.** They will be pulled into the main branch later.
This repo is for testing development, Major overhauls may happen often.
## Roadmap
- [x] Landing page
- [x] Home page
- [x] Navbar
- [x] Footer
This is the development repository of `rowblox`.
**PLEASE** prototype ideas here before committing your ideas to the production repository.
Some1 please add all the features i am not going to remember everything
Hai
## Contributors
- row
- riot
- Dudebloke
- calones (i-have-an-issue)
- unexp
- salem
- kinery
- goom

553
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
"private": true,
"scripts": {
"start": "node build/",
"dev": "vite dev",
"dev": "vite dev --port 80",
"build": "vite build",
"preview": "vite preview",
"lint": "prettier --plugin-search-dir . --check .",
@ -24,6 +24,9 @@
},
"type": "module",
"dependencies": {
"@sveltejs/adapter-node": "^1.0.0-next.96"
"@sveltejs/adapter-node": "^1.0.0-next.96",
"bcrypt": "^5.1.0",
"cookie": "^0.5.0",
"mongodb": "^4.10.0"
}
}

View File

@ -29,6 +29,15 @@ body {
display: none;
}
.line-clamp {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
line-clamp: 1;
-webkit-box-orient: vertical;
}
@keyframes scrollbg {
0% {
background-position: 0 0;

View File

@ -1,8 +1,32 @@
import { COOKIE_NAME } from "$lib/constants";
import { getUserFromSession } from "$lib/database";
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
if (event.url.pathname !== "/maintenance" && process.env.MAINTENANCE) {
return new Response("", { status: 302, headers: { Location: "/maintenance" } });
}
const cookie = event.cookies.get(COOKIE_NAME);
if (!cookie) {
if (event.routeId.startsWith("(app)")) return new Response("", { status: 302, headers: { Location: "/landing" } });
return await resolve(event);
}
let user = await getUserFromSession(cookie, event.request.headers.get("x-forwarded-for") || event.getClientAddress());
if (!user) {
event.cookies.delete(COOKIE_NAME, { secure: !!process.env.PRODUCTION });
if (event.routeId.startsWith("(app)")) return new Response("", { status: 302, headers: { Location: "/landing" } });
} else
event.locals.user = {
_id: user._id,
username: user.username,
currency: user.currency,
thumbnails: {
headshot: "/img/headshot.png",
bodyshot: "https://media.tenor.com/Lo0GvkoTFR4AAAAd/xbox-xbox-avatar.gif"
}
};
return await resolve(event);
}

View File

@ -0,0 +1,7 @@
<a href="/games/1">
<img
class="w-full min-h-28 max-h-28 mb-4"
src="https://cdn.discordapp.com/attachments/1025864353452400812/1028718186700480513/058f09be8a19b8607d6fe50baa6659a1-AdBannerTemplate.png"
alt="Your ad here."
/>
</a>

View File

@ -0,0 +1,3 @@
<div class="container flex bg-blue-600 outline outline-1 outline-blue-700 items-center justify-center mb-2 mt-[-0.5rem]">
<p class="text-white">This is an example alert</p>
</div>

View File

@ -1,7 +1,13 @@
<div class="shadow-lg max-w-[15rem] p-2 rounded-lg border-2 border-gray-300 mt-2 mx-1 inline-block">
<a href="/games/1">
<img class="rounded mb-1" src="/img/background.png" alt="" />
<p class="font-bold">Killing row simulator</p>
<img class="mb-1" src="/img/background.png" alt="" />
<p class="font-bold line-clamp">Killing row simulatorKilling row simulatorKilling row simulatorKilling row simulatorKilling row simulator</p>
<p class="text-sm">0 Players Online</p>
<div class="flex text-xs pt-1">
<p class="px-1.5 bg-zinc-500 rounded-md text-white mr-0.5">Offline</p>
<p class="px-1.5 bg-green-500 rounded-md text-white mr-0.5">Online</p>
<p class="px-1.5 bg-blue-500 rounded-md text-white mr-0.5">2015</p>
<p class="px-1.5 bg-blue-500 rounded-md text-white mr-0.5">2013</p>
</div>
</a>
</div>

View File

@ -1,6 +1,6 @@
<div class="max-w-[6rem] inline-block">
<a href="/users/1">
<img class="rounded-full border-2 border-grey-300 w-24 h-24 shadow-lg" src="" alt="" />
<p class="text-center">Rowblox</p>
<a href="/users/1/profile">
<img class="rounded-full border-2 border-grey-600 w-24 h-24 shadow-lg" src="https://cdn.discordapp.com/attachments/1026648537741672490/1029210746501996616/2022.10-550.png" alt="" />
<p class="text-center truncate">Only16Characters</p>
</a>
</div>

View File

@ -0,0 +1,61 @@
<!-- https://github.com/BillBuilt/sveltekit-hcaptcha -->
<script>
import { onMount, onDestroy } from "svelte";
import { browser } from "$app/environment";
import { HCAPTCHA_SITEKEY } from "$lib/constants";
let hcaptcha;
let hcaptchaWidgetID;
export let token = null;
// forces 1-way binding
let captchaToken;
$: token = captchaToken;
export let isValid = false;
// forces 1-way binding
let captchaValid;
$: isValid = captchaValid;
onMount(() => {
setTimeout(function () {
if (browser) {
hcaptcha = window.hcaptcha;
if (hcaptcha.render) {
hcaptchaWidgetID = hcaptcha.render("hcaptcha", {
sitekey: HCAPTCHA_SITEKEY,
size: "normal",
callback: onValidCaptcha,
"error-callback": onErrorCaptcha,
theme: "light"
});
}
}
}, 500);
});
onDestroy(() => {
if (browser) {
hcaptcha = null;
}
});
function onValidCaptcha(e) {
//console.log('verified event', e)
captchaToken = e;
captchaValid = true;
}
function onErrorCaptcha(e) {
//console.log('error event', {error: e.error})
captchaToken = null;
captchaValid = false;
hcaptcha.reset(hcaptchaWidgetID);
}
</script>
<svelte:head>
<script src="https://js.hcaptcha.com/1/api.js?render=explicit" async defer></script>
</svelte:head>
<div id="hcaptcha" class="h-captcha" />

20
src/lib/constants.js Normal file
View File

@ -0,0 +1,20 @@
export const COOKIE_NAME = ".ROWBLOX_SESSION_DO_NOT_SHARE";
export const USERNAME_REGEX = /[^A-Za-z0-9\-_ ]/g;
export const MIN_USERNAME_LENGTH = 3;
export const MAX_USERNAME_LENGTH = 16;
export const MIN_PASSWORD_LENGTH = 3;
export const INVITE_KEY_PREFIX = "rowblox-";
export const SESSION_EXPIRE = 604800000;
export const HCAPTCHA_SITEKEY = "be5c40c1-13db-423c-878e-f3428e9fc841";
export const RANKS = {
0: "Banned",
1: "Member",
2: "Booster",
3: "Administrator",
4: "Developer",
5: "NO_ACCESS_MAX_PERMISSIONS"
};
export const GAME_VERSIONS = {
0: "2013",
1: "2015"
};

View File

@ -0,0 +1,128 @@
import { MongoClient } from "mongodb";
import { hash, compare } from "bcrypt";
import { prerendering } from "$app/environment";
import { randomBytes, randomUUID } from "crypto";
import { INVITE_KEY_PREFIX, SESSION_EXPIRE } from "$lib/constants";
if (!process.env.MONGO_URL) throw new Error("Missing MONGO_URL env variable!");
const client = new MongoClient(process.env.MONGO_URL);
if (!prerendering) await client.connect();
const db = await client.db("rowblox");
const users = await db.collection("users");
const games = await db.collection("games");
const assets = await db.collection("assets");
const invites = await db.collection("invites");
const avatars = await db.collection("avatars");
const sessions = await db.collection("sessions");
const friends = await db.collection("friends");
async function inc(collection) {
await collection.updateOne({ _id: "_inc" }, { $inc: { _inc: 1 } });
return (await collection.findOne({ _id: "_inc" }))._inc;
}
async function hashPassword(plaintext) {
return new Promise((resolve, reject) => {
hash(plaintext, 10, (err, hash) => resolve(hash));
});
}
export async function compareHash(plaintext, hash) {
return new Promise((resolve, reject) => {
compare(plaintext, hash, (err, result) => resolve(result));
});
}
function genSession() {
return randomBytes(64).toString("base64");
}
function genInvite() {
return `${INVITE_KEY_PREFIX}${randomUUID()}`;
}
export async function createUser(username, password, lastip) {
const _id = await inc(users);
const user = {
_id,
username,
password: await hashPassword(password),
lastip,
currency: 100,
joinDate: Date.now(),
lastOnline: Date.now(),
rank: 1,
activity: 1,
pendingFriendRequests: [],
bio: "This is my Rowblox profile!"
};
const avatar = {
_id,
BodyColors: {
HeadColor: 1,
TorsoColor: 1,
LeftArmColor: 1,
RightArmColor: 1,
LeftLegColor: 1,
RightLegColor: 1
},
Wearing: [],
Inventory: []
};
await Promise.all([avatars.insertOne(avatar), await users.insertOne(user)]);
return _id;
}
export async function createSession(_id, lastip) {
const user = await users.findOne({ _id });
if (!user) return false;
const session = genSession();
await sessions.insertOne({
_id: session,
owner: _id,
expires: Date.now() + SESSION_EXPIRE
});
await users.updateOne({ _id: user._id }, { $set: { lastip } });
return session;
}
export async function getUserFromSession(session, lastip) {
const sessionDocument = await sessions.findOne({ _id: session });
if (!sessionDocument) return false;
const user = await users.findOne({ _id: sessionDocument.owner });
if (!user) return false;
await users.updateOne({ _id: user._id }, { $set: { lastip } });
return user;
}
export async function deleteSession(session, lastip) {
const sessionDocument = await sessions.findOne({ _id: session });
if (!sessionDocument) return false;
const user = await users.findOne({ _id: sessionDocument.owner });
if (!user) return false;
await users.updateOne({ _id: user._id }, { $set: { lastip } });
return await sessions.deleteOne({ _id: session });
}
export async function getUser(query, projection) {
const user = await users.findOne(query, { projection });
if (!user) return false;
return user;
}

View File

@ -1,7 +1,7 @@
import crypto from "crypto";
const privatekey = `-----BEGIN RSA PRIVATE KEY-----
${process.env.PRIVATE_KEY}
MIICXgIBAAKBgQDJ9itcSsBa9FNyE1jBn4OBfOYsMSSJWyi0Jgn6qXNvSzLks7INwmkqejrEc4WOdUNjQ6XaoAK/vmBf7oMQJ+xuDl47rYVqLGW0JAKPSbn0QA19bmr3eSIyUeqrhfwRGn4YLBDm6CMu+m8P/VaDU1qSxPd2PuIpLmTlKFMcF+HWEQIDAQABAoGBAL49+x5W89c5q5kbjFHnlpLVOmSKbiZNDoyUAHZ0RF6j8W7prmGzrijrNoxzXW2SHEZXJNZKQAyqolH7dM41LUnaIWizNoIkuJKIb+HWzQlnu75KLNyVDtlMyTNnwTfHkAQ6vmRv8f8S74ZXlj+SKNVjd0p0R+TSqh4NHzNZkNC5AkEA33wCzFtvZJv5cVbc8Ak8VmmSdoPN9HBHuJ1BJ/VTYrd9NmGoxbb2Ixfc3kVItD1NMLs9Jo3x8VKyC0fP90KAIwJBAOdYgtK4QGJHco48J37K3g9r4Kjidv933ADnkZefhTGE4ycakv5gcvnMWbcjHRPv3a7r39+ukgsbNB7BdyKWWjsCQBsewHQuMGFkMCwZ32vdow3Vd+mb6xVbvshfhPWlZr4XCEHeLg34OvxdO/dZLw54VfKw9iXEmfSwFV0bFNiroEMCQQCB05AHBNNM09+bpnJbmykm6lk3LW+uSesyrsFrn1+1vGdlSGp5SlL7kAxA0/m7eH6lbUVDV8opZWjIYbWjuVCFAkEAv8rLnjBwqbNY1O67a4rXh4DJZ7lHm5meX7+YcW5haB4GGyZpikVXYEcfwcM5A03dTKgljLbcDT8diO5RTOsDEg==
-----END RSA PRIVATE KEY-----`;
export default function (input) {

View File

@ -2,6 +2,10 @@
import { page } from "$app/stores";
</script>
<svelte:head>
<title>{$page.status == 404 ? "Page Not Found" : "Unexpected Error"} - Rowblox</title>
</svelte:head>
<div class="mx-auto rounded-lg shadow border border-gray-300 max-w-2xl text-center divide-y">
<div class="p-2">
<img class="mx-auto mb-2" src="/img/error.png" alt="" />

View File

@ -0,0 +1,4 @@
/** @type {import('./$types').LayoutServerLoad} */
export function load({ locals }) {
return { user: locals.user };
}

View File

@ -1,3 +1,17 @@
<script>
import Alert from "$lib/components/Alert.svelte";
import Banner from "$lib/components/AdBanner.svelte";
import { goto } from "$app/navigation";
/** @type {import('./$types').LayoutData} */
export let data;
async function logout() {
const response = await fetch("/logout", { method: "GET" });
await goto("/landing");
}
</script>
<div class="navbar scrolling-background text-lg py-1 text-white">
<div class="container flex items-center justify-end">
<a href="/">
@ -5,33 +19,31 @@
</a>
<a class="mr-7" href="/games">Games</a>
<a class="mr-7" href="/catalog">Catalog</a>
<a class="mr-7" href="/forums">Forums</a>
<a class="mr-7" href="https://discord.gg/3wRpA5bzU9">Discord</a>
<input class="rounded px-2 border-2 border-grey-300 text-black flex-grow mr-10" type="text" name="search" id="search" placeholder="Search" />
<a class="mr-4 px-2 py-1 flex items-center rounded bg-gradient-to-tr from-blue-700 to-blue-600 outline outline-blue-800 outline-1 shadow-md hover:shadow-xl" href="/my/money">
<a class="mr-1 px-2 py-1 flex items-center rounded bg-gradient-to-tr from-blue-700 to-blue-600 outline outline-blue-800 outline-1 shadow-md hover:shadow-xl" href="/my/money">
<img class="h-6 mr-1.5" src="/img/rowbux.png" alt="" />
100
</a>
<p class="text-2xl mb-0.5">|</p>
<div class="ml-2 relative inline-block dropdown">
<button class="hover:text-zinc-200" href="/my/profile"
>WWWWWWWWWWWWWWWW
<div class="pl-1.5 ml-1 relative inline-block dropdown">
<button class="hover:text-zinc-200 truncate" href="/my/profile"
>{data.user?.username}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-4 h-4 inline-block">
<path fill-rule="evenodd" d="M12.53 16.28a.75.75 0 01-1.06 0l-7.5-7.5a.75.75 0 011.06-1.06L12 14.69l6.97-6.97a.75.75 0 111.06 1.06l-7.5 7.5z" clip-rule="evenodd" />
</svg>
</button>
<div class="absolute bg-white text-center text-black w-36 right-0 origin-top-right rounded border border-gray-300 divide-y shadow-lg hidden dropdown-content">
<div>
<a class="block px-4 py-0.5 text-gray-700" href="/my/profile">Profile</a>
<a class="block px-4 py-0.5 text-gray-700" href="/users/{data.user?._id}/profile">Profile</a>
<a class="block px-4 py-0.5 text-gray-700" href="/my/settings">Settings</a>
</div>
<a class="block px-4 py-0.5 text-red-700" href="/logout">Logout</a>
<span class="block px-4 py-0.5 text-red-700 hover:cursor-pointer" on:click={logout}>Logout</span>
</div>
</div>
</div>
</div>
<div class="navbar bg-blue-900 text-base py-0.5 text-white">
<div class="navbar bg-gray-800 text-base py-0.5 text-white">
<div class="container flex items-center">
<a class="mr-6" href="/my/avatar">Avatar</a>
<a class="mr-6" href="/my/friends">Friends</a>
@ -49,6 +61,23 @@
</div>
</div>
<div class="text-black container mt-4">
<div class="text-black container mt-4 min-h-[100vh]">
<Alert />
<!--<Banner />-->
<slot />
</div>
<div class="flex text-white items-center justify-start scrolling-background mt-4">
<div class="container">
<div class="flex text-center py-2 border-b border-blue-500">
<a class="text-lg flex-auto" href="/about/tos">Terms of Service</a>
<a class="text-lg flex-auto" href="/about/rules">Rules</a>
<a class="text-lg flex-auto" href="/about/privacy">Privacy Policy</a>
<a class="text-lg flex-auto" href="/about/credits">Credits</a>
</div>
<div class="p-2">
<img class="w-72 mr-6 justify-start inline saturate-150" src="/img/banner.png" alt="" />
<h1 class="justify-end inline">©{new Date().getFullYear()} Rowblox Corporation. Rest in peace popbob 🙏🙏🙏</h1>
</div>
</div>
</div>

View File

@ -1,6 +1,7 @@
<script>
import Game from "$lib/components/GameListed.svelte";
import Friend from "$lib/components/UserListed.svelte";
import { page } from "$app/stores";
</script>
<svelte:head>
@ -8,26 +9,16 @@
</svelte:head>
<div class="flex items-center">
<img class="rounded-full border-2 border-grey-300 w-36 h-36 mx-2 shadow-lg" src="" alt="" />
<p class="mx-2 font-bold text-3xl">Hello, Only16Characters!</p>
<img class="rounded-full border-2 border-grey-600 w-36 h-36 mx-2 shadow-lg" src={$page.data.user?.thumbnails.headshot} alt="" />
<p class="mx-2 font-bold text-3xl">Hello, {$page.data.user?.username}!</p>
</div>
<div class="my-2">
<div class="my-2 min-h-[11rem]">
<div class="flex items-center mb-2">
<p class="font-bold text-2xl my-1">Friends</p>
<span class="flex-grow" />
<a class="text-lg px-3 py-0.5 rounded border-2 border-blue-500 shadow-lg cursor-pointer hover:bg-blue-200 text-blue-500" href="/my/friends">View All</a>
</div>
<Friend />
<Friend />
<Friend />
<Friend />
<Friend />
<Friend />
<Friend />
<Friend />
<Friend />
<Friend />
</div>
<div class="my-2">
@ -36,12 +27,4 @@
<span class="flex-grow" />
<a class="text-lg px-3 py-0.5 rounded border-2 border-blue-500 shadow-lg cursor-pointer hover:bg-blue-200 text-blue-500" href="/games">View All</a>
</div>
<Game />
<Game />
<Game />
<Game />
<Game />
<Game />
<Game />
<Game />
</div>

View File

@ -13,13 +13,3 @@
<button class="text-lg px-3 py-0.5 rounded border-2 border-blue-500 shadow-lg cursor-pointer hover:bg-blue-200 text-blue-500 mr-2">Search</button>
<a class="text-lg px-3 py-0.5 rounded border-2 border-blue-500 shadow-lg cursor-pointer hover:bg-blue-200 text-blue-500" href="/games/create">Create Game</a>
</div>
<Game />
<Game />
<Game />
<Game />
<Game />
<Game />
<Game />
<Game />
<Game />

View File

@ -0,0 +1,6 @@
import { error } from "@sveltejs/kit";
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
throw error(404, "That game doesn't exist");
}

View File

@ -0,0 +1,111 @@
<script>
import UserListed from "$lib/components/UserListed.svelte";
export let game;
let state = 0;
function updateState(newState) {
state = newState;
}
</script>
<svelte:head>
<title>{game?.name} - Rowblox</title>
</svelte:head>
<div class="mx-auto rounded-lg shadow border border-gray-300 p-4 divide-y">
<div class="flex">
<img class="float-left h-[22.5rem]" alt="" src="/img/background.png" />
<div class="w-full flex flex-col pl-4">
<div class="max-w-[22.9rem]">
<p class="text-3xl font-bold truncate">{game?.name}</p>
<p class="text-zinc-500">By <a class="font-bold text-blue-500" href="/users/{game?.creator?._id}">{game?.creator?.username}</a></p>
</div>
<div class="flex text-sm pt-1">
<p class="px-2 bg-zinc-500 rounded-md text-white mr-1">Offline</p>
<p class="px-2 bg-green-500 rounded-md text-white mr-1">Online</p>
<p class="px-2 bg-blue-500 rounded-md text-white mr-1">2015</p>
<p class="px-2 bg-blue-500 rounded-md text-white mr-1">2013</p>
</div>
<span class="flex-grow" />
<div class="flex">
<a class="flex-auto text-lg text-blue-500 px-4 py-0.5 rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500 mr-0.5 mb-1 text-center" href="/games/{game?.id}/edit">
Edit
</a>
<button class="flex-auto text-lg text-blue-500 px-4 py-0.5 rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500 ml-0.5 mb-1">Host</button>
</div>
<button class="text-2xl text-blue-500 px-4 py-2 rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500">Play</button>
</div>
</div>
</div>
<div class="rounded-lg shadow border border-gray-300 p-2 mt-4">
<div class="flex flex-row text-xl text-center text-black">
<button
class="flex-auto {state == 0 && 'font-bold text-black text-opacity-30'}"
on:click={() => {
updateState(0);
}}>About</button
>
<button
class="flex-auto {state == 1 && 'font-bold text-black text-opacity-30'}"
on:click={() => {
updateState(1);
}}>Store</button
>
<button
class="flex-auto {state == 2 && 'font-bold text-black text-opacity-30'}"
on:click={() => {
updateState(2);
}}>Players</button
>
</div>
</div>
{#if state == 0}
<div class="rounded-lg shadow border border-gray-300 p-2 mt-4 divide-y">
<div class="pb-2">
<p class="px-2 py-1 font-bold text-xl">Description</p>
<p class="px-2 text-black text-opacity-80">Test Game</p>
</div>
<div class="flex flex-row pb-2">
<div class="mx-4 mt-4 text-center flex-grow">
<p class="text-zinc-500 inline-block">Players</p>
<p class="">0</p>
</div>
<div class="mx-4 mt-4 text-center flex-grow">
<p class="text-zinc-500 inline-block">Visits</p>
<p class="">0</p>
</div>
<div class="mx-4 mt-4 text-center flex-grow">
<p class="text-zinc-500 inline-block">Created</p>
<p class="">10/10/22</p>
</div>
<div class="mx-4 mt-4 text-center flex-grow">
<p class="text-zinc-500 inline-block">Updated</p>
<p class="">10/10/22</p>
</div>
<div class="mx-4 mt-4 text-center flex-grow">
<p class="text-zinc-500 inline-block">Max Players</p>
<p class="">No Limit</p>
</div>
</div>
</div>
{:else if state == 1}
<div class="rounded-lg shadow border border-gray-300 p-2 mt-4">
<p class="px-2 py-1 font-bold text-xl">Gamepasses</p>
<p class="px-2 text-black text-opacity-80">This store does not have any gamepasses for sale.</p>
</div>
{:else if state == 2}
<div class="rounded-lg shadow border border-gray-300 p-2 mt-4">
<p class="px-2 py-1 font-bold text-xl">Players</p>
<p class="px-2 text-black text-opacity-80">This game does not have any players.</p>
</div>
<div class="rounded-lg shadow border border-gray-300 p-2 mt-4">
<p class="px-2 py-1 font-bold text-xl mb-1">Players</p>
<UserListed />
<UserListed />
<UserListed />
<UserListed />
</div>
{/if}

View File

@ -2,30 +2,40 @@
<title>Invites - Rowblox</title>
</svelte:head>
<div class="d-flex justify-content-between">
<h1 class="text-4xl mt-32 text-bold font-bold">Invite Keys</h1>
<p>"keys can be created every 7 days. Creating a key costs 250 Rowbux."</p>
<button class=" text-white px-4 py-1 w-full rounded shadow-lg border-2 border-white bg-green-500 hover:bg-green-400">Create a Key</button>
<div>
<table class="table-auto mx-auto text-semibold font-bold mt-4 text-1.5xl w-96 h-20 rounded shadow-lg border-2">
<thead>
<tr class="divide-x">
<th>Key</th>
<th>Created</th>
<th>Uses</th>
</tr>
</thead>
<tbody>
<tr class="divide-x">
<td>Keytest1</td>
<td>10/8/22</td>
<td>100</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="d-flex justify-content-between">
<div class="flex place-content-between">
<div>
<h1 class="text-3xl text-bold font-bold">Invite Keys</h1>
<p>Invite Keys can be created every 7 days for 250 Rowbux each.</p>
</div>
<div class="flex flex-col">
<button class="my-auto px-3 py-1 rounded border-2 border-green-500 text-green-500 shadow-lg hover:bg-green-200">Create Invite Key</button>
</div>
</div>
<div>
<table class="table-auto text-center w-full border-2 border-gray-300 mt-2 divide-y">
<thead>
<tr class="divide-x">
<th>Invite Key</th>
<th>Claimed By</th>
<th>Created</th>
<th>Revoke</th>
</tr>
</thead>
<tbody class="divide-y">
<tr class="divide-x">
<td class="truncate">jahseh-45c61898-ddbb-414c-9cc2-4e6a35f164c8</td>
<td>Nobody</td>
<td>{new Date().toLocaleString()}</td>
<td><button class="my-1 px-6 py-1 rounded border-2 border-red-500 text-red-500 shadow-lg hover:bg-red-200">Revoke</button></td>
</tr>
<tr class="divide-x">
<td class="truncate">jahseh-67cb4676-071a-4160-8f18-af494ff15b80</td>
<td><a class="text-blue-500" href="/users/1">Rowblox</a></td>
<td>{new Date(1665297610952).toLocaleString()}</td>
<td><button class="my-1 px-6 py-1 rounded border-2 border-red-300 text-red-300 shadow-lg cursor-default disabled">Revoke</button></td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,10 @@
import { error } from "@sveltejs/kit";
import { getUser } from "$lib/database";
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
const user = await getUser({ _id: Number(params.id) }, { username: true, currency: true, joinDate: true, lastOnline: true, rank: true, activity: true, bio: true });
if (!user) throw error(404, "That user doesn't exist");
return { profile: user };
}

View File

@ -0,0 +1,52 @@
<script>
export let data;
const joinDate = new Date(data.profile?.joinDate);
const lastOnline = new Date(data.profile?.lastOnline);
</script>
<div class="mx-auto rounded-lg flex shadow border border-gray-300 px-2 py-1.5">
<img class="rounded-full border-2 border-grey-600 h-28 shadow-lg mr-2" src="/img/headshot.png" alt="" />
<div class="flex flex-col place-content-between pt-2 pb-1.5">
<p class="text-3xl font-bold">{data.profile?.username}</p>
<div class="flex text-lg">
<p class="mr-4"><a class="font-bold" href="/users/id/friends">0</a> Friends</p>
<p class="mr-4"><a class="font-bold" href="/users/id/followers">0</a> Followers</p>
<p><a class="font-bold" href="/users/id/following">0</a> Following</p>
</div>
</div>
<span class="flex-grow" />
<div class="flex place-content-between place-self-end py-1">
<a class="mr-2 px-3 py-1 rounded border-2 border-gray-500 text-gray-500 shadow-lg hover:bg-gray-200" href="/users/id/message">Message</a>
<button class="px-3 py-1 rounded border-2 border-blue-500 text-blue-500 shadow-lg hover:bg-blue-200">Add Friend</button>
</div>
</div>
<div class="flex flex-row my-2">
<div class="w-full rounded-lg shadow border border-gray-300 p-4">
<p class="text-2xl font-bold">About</p>
<div class="divide-y">
<img class="mx-auto w-96" src="/img/bodyshot.png" alt="" />
<p class="text-center py-2">{data.profile?.bio}</p>
<div class="flex flex-row text-center pt-2">
<div class="flex flex-col mx-auto">
<p class="font-bold text-zinc-400">Join Date</p>
<p>{joinDate.getMonth() + 1}/{joinDate.getDate()}/{joinDate.getFullYear()}</p>
</div>
<div class="flex flex-col mx-auto">
<p class="font-bold text-zinc-400">Last Online</p>
<p>{lastOnline.getMonth() + 1}/{lastOnline.getDate()}/{lastOnline.getFullYear()}</p>
</div>
</div>
</div>
</div>
<div class="flex flex-col w-full ml-1">
<div class="border border-gray-300 rounded-lg w-full flex-auto mb-1 shadow p-4">
<p class="text-2xl font-bold">Games</p>
</div>
<div class="border border-gray-300 rounded-lg w-full flex-auto shadow p-4">
<p class="text-2xl font-bold">Friends</p>
</div>
</div>
</div>

View File

@ -0,0 +1,24 @@
import { invalid, redirect } from "@sveltejs/kit";
import { getUser, compareHash, createSession } from "$lib/database";
import { MIN_USERNAME_LENGTH, MAX_USERNAME_LENGTH, USERNAME_REGEX, MIN_PASSWORD_LENGTH, INVITE_KEY_PREFIX, COOKIE_NAME } from "$lib/constants";
/** @type {import('./$types').Actions} */
export const actions = {
default: async ({ cookies, request, getClientAddress }) => {
const session = cookies.get(COOKIE_NAME);
if (session) throw redirect(302, "/");
const data = await request.formData();
const username = data.get("username");
const password = data.get("password");
const user = await getUser({ username }, { password: true });
if (!user) return invalid(400, { error: "username" });
const correctPassword = await compareHash(password, user.password);
if (!correctPassword) return invalid(400, { error: "password" });
cookies.set(COOKIE_NAME, await createSession(user._id, request.headers.get("x-forwarded-for") || getClientAddress()), { secure: !!process.env.PRODUCTION });
throw redirect(302, "/");
}
};

View File

@ -1,12 +1,35 @@
<script>
/** @type {import('./$types').ActionData} */ export let form;
</script>
<svelte:head>
<title>Login - Rowblox</title>
</svelte:head>
<div class="max-w-lg mx-auto text-center text-white">
<img class="mx-auto" src="/favicon.png" alt="" />
<form method="POST" class="max-w-lg mx-auto text-center text-white">
<img class="mx-auto mb-2" src="/favicon.png" alt="" />
<div class="flex flex-col">
<input class="rounded-lg w-72 px-4 border-2 border-blue-500 text-black mx-auto my-1.5 py-2" type="username" name="username" id="username" placeholder="Username" />
<input class="rounded-lg w-72 px-4 border-2 border-blue-500 text-black mx-auto my-1.5 py-2" type="password" name="password" id="password" placeholder="Password" />
<input
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'username' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
type="username"
name="username"
id="username"
placeholder="Username"
required
value={form?.username ?? ""}
/>
<input
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'password' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
type="password"
name="password"
id="password"
placeholder="Password"
required
/>
</div>
<button class="my-1.5 text-xl text-white px-4 py-1 w-full rounded shadow-lg border-2 border-blue-500">Login</button>
</div>
{#if form?.message}
<p class="rounded w-[19rem] px-4 border-[3px] border-red-500 text-red-500 mx-auto my-1.5 py-1.5 hover:bg-opacity-50 hover:bg-red-500">{form?.message}</p>
{/if}
<div class="h-captcha" data-sitekey="30000000-ffff-ffff-ffff-000000000003" />
<button class="my-1.5 text-xl text-white px-4 py-1.5 w-[19rem] rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500">Login</button>
</form>

View File

@ -0,0 +1,20 @@
import { deleteSession } from "$lib/database";
import { COOKIE_NAME } from "$lib/constants";
/** @type {import('./$types').RequestHandler} */
export async function GET({ request, cookies, getClientAddress }) {
const session = cookies.get(COOKIE_NAME);
if (!session)
return new Response("", {
status: 302,
headers: { location: "/landing" }
});
cookies.delete(COOKIE_NAME, { secure: !!process.env.PRODUCTION });
await deleteSession(session, request.headers.get("x-forwarded-for") || getClientAddress());
return new Response("", {
status: 302,
headers: { location: "/landing" }
});
}

View File

@ -0,0 +1,72 @@
import { invalid, redirect } from "@sveltejs/kit";
import { createUser, createSession, getUser } from "$lib/database";
import { MIN_USERNAME_LENGTH, MAX_USERNAME_LENGTH, USERNAME_REGEX, MIN_PASSWORD_LENGTH, INVITE_KEY_PREFIX, COOKIE_NAME, HCAPTCHA_SITEKEY } from "$lib/constants";
/** @type {import('./$types').Actions} */
export const actions = {
default: async ({ cookies, request, getClientAddress }) => {
const session = cookies.get(COOKIE_NAME);
if (session) throw redirect(302, "/landing");
const data = await request.formData();
const username = data.get("username");
const password = data.get("password");
const confirm_password = data.get("confirm_password");
const invite_key = data.get("invite_key");
const hcaptcha_response = data.get("h-captcha-response");
if (username.length < MIN_USERNAME_LENGTH || username.length > MAX_USERNAME_LENGTH || new RegExp(USERNAME_REGEX).test(username))
return invalid(400, {
username,
invite_key,
error: "username"
});
if (username.length < MIN_PASSWORD_LENGTH || confirm_password !== password)
return invalid(400, {
username,
invite_key,
error: "password"
});
if (!invite_key.startsWith(INVITE_KEY_PREFIX))
return invalid(400, {
username,
invite_key,
error: "invite_key"
});
if (!hcaptcha_response)
return invalid(400, {
username,
invite_key,
error: "hcaptcha"
});
const existingUser = await getUser({ username }, { _id: true });
if (existingUser)
return invalid(400, {
username,
invite_key,
error: "username"
});
const hcaptcha = await fetch("https://hcaptcha.com/siteverify", {
method: "POST",
body: `response=${hcaptcha_response}&secret=${process.env.HCAPTCHA_SECRET}`,
headers: { "content-type": "application/x-www-form-urlencoded" }
});
const hcaptchaBody = await hcaptcha.json();
if (!hcaptchaBody.success)
return invalid(400, {
username,
invite_key,
error: "hcaptcha"
});
const user = await createUser(username, password, request.headers.get("x-forwarded-for") || getClientAddress());
cookies.set(COOKIE_NAME, await createSession(user, request.headers.get("x-forwarded-for") || getClientAddress()), { secure: !!process.env.PRODUCTION });
throw redirect(302, "/");
}
};

View File

@ -1,14 +1,58 @@
<script>
import HCaptcha from "../../../lib/components/hCaptcha.svelte";
/** @type {import('./$types').ActionData} */ export let form;
let captchaValid = false;
let captchaToken;
</script>
<svelte:head>
<title>Register - Rowblox</title>
</svelte:head>
<div class="max-w-lg mx-auto text-center text-white">
<img class="mx-auto" src="/favicon.png" alt="" />
<form method="POST" class="max-w-lg mx-auto text-center text-white">
<img class="mx-auto mb-2" src="/favicon.png" alt="" />
<div class="flex flex-col">
<input class="rounded-lg w-72 px-4 border-2 border-blue-500 text-black mx-auto my-1.5 py-2" type="username" name="username" id="username" placeholder="Username" />
<input class="rounded-lg w-72 px-4 border-2 border-blue-500 text-black mx-auto my-1.5 py-2" type="password" name="password" id="password" placeholder="Password" />
<input class="rounded-lg w-72 px-4 border-2 border-blue-500 text-black mx-auto my-1.5 py-2" type="password" name="passwordconfirm" id="passwordconfirm" placeholder="Confirm Password" />
<input class="rounded-lg w-72 px-4 border-2 border-blue-500 text-black mx-auto my-1.5 py-2" type="text" name="invite" id="invite" placeholder="Invite Key" />
<input
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'username' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
required
type="username"
name="username"
id="username"
placeholder="Username"
value={form?.username ?? ""}
/>
<input
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'password' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
required
type="password"
name="password"
id="password"
placeholder="Password"
/>
<input
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'password' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
required
type="password"
name="confirm_password"
id="confirm_password"
placeholder="Confirm Password"
/>
<input
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'invite_key' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto mt-0.5 mb-1.5 py-2"
required
type="text"
name="invite_key"
id="invite_key"
placeholder="Invite Key"
value={form?.invite_key ?? ""}
/>
</div>
<button class="my-1.5 text-xl text-white px-4 py-1 w-full rounded shadow-lg border-2 border-blue-500">Register</button>
</div>
{#if form?.message}
<p class="rounded w-[19rem] px-4 border-2 border-red-500 text-red-500 bg-red-500 mx-auto my-1.5 py-1.5 bg-opacity-50">{form?.message}</p>
{/if}
<div class={form?.error == "hcaptcha" && "border-2 border-red-500"}>
<HCaptcha bind:token={captchaToken} bind:isValid={captchaValid} />
</div>
<button class="my-1.5 text-xl text-white px-4 py-1.5 w-[19rem] rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500">Register</button>
</form>

View File

View File

@ -1 +0,0 @@
{"data":["0fb35a870ca24bea2a17020144335cd7","0fb35a870ca24bea2a17020144335cd7"]}

View File

@ -1 +0,0 @@
{"data":["0.206.0pcplayer"]}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
static/img/bodyshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
static/img/bucks.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
static/img/headshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB