mirror of
https://git.suyu.dev/suyu/website.git
synced 2025-12-24 08:14:38 +01:00
comment out friends-related stuff
This commit is contained in:
parent
558fcb16b6
commit
3e017789f3
18 changed files with 671 additions and 67 deletions
|
|
@ -6,6 +6,41 @@
|
|||
import type { PageData } from "../routes/$types";
|
||||
import cookie from "cookiejs";
|
||||
|
||||
interface AnchorItem {
|
||||
type: "anchor";
|
||||
name: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
interface ButtonItem {
|
||||
type: "button";
|
||||
name: string;
|
||||
action: () => void;
|
||||
}
|
||||
|
||||
interface DividerItem {
|
||||
type: "divider";
|
||||
}
|
||||
|
||||
type NavItem = AnchorItem | ButtonItem | DividerItem;
|
||||
|
||||
export let navItems: NavItem[] = [
|
||||
{ type: "anchor", name: "Multiplayer", href: "/account" },
|
||||
{
|
||||
type: "anchor",
|
||||
name: "Profile",
|
||||
href: "https://gravatar.com/profile",
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
type: "button",
|
||||
name: "Sign out",
|
||||
action: signOut,
|
||||
},
|
||||
];
|
||||
|
||||
export let user: PageData["user"];
|
||||
const token = getContext<Writable<string>>("token");
|
||||
|
||||
|
|
@ -24,7 +59,10 @@
|
|||
open = !open;
|
||||
}
|
||||
|
||||
let jsEnabled = false;
|
||||
|
||||
onMount(() => {
|
||||
jsEnabled = true;
|
||||
function closeMenu(e: MouseEvent) {
|
||||
if (e.target instanceof HTMLElement) {
|
||||
if (!e.target.closest(".user-profile-menu")) {
|
||||
|
|
@ -48,22 +86,47 @@
|
|||
style="transition: 360ms {transition}"
|
||||
class={`${open ? "rotate-0 scale-100 opacity-100" : "-rotate-90 scale-0 opacity-0"} absolute right-0 top-full mt-2 flex h-fit origin-top-right transform-gpu flex-col overflow-hidden rounded-[20px] rounded-tr-none border-2 border-solid border-[#ffffff34] bg-[#110d10] p-[2px] opacity-0 shadow-lg shadow-[rgba(0,0,0,0.25)] motion-reduce:transition-none [&>.nav-btn:first-child]:rounded-tl-[16px] [&>.nav-btn:first-child]:rounded-tr-none [&>.nav-btn:last-child]:rounded-bl-[16px] [&>.nav-btn:last-child]:rounded-br-[16px]`}
|
||||
>
|
||||
<div
|
||||
role="button"
|
||||
class="nav-btn flex items-center whitespace-nowrap hover:bg-[#1d1d1d] [&>*]:w-full [&>*]:px-4 [&>*]:py-2 [&>*]:text-left"
|
||||
>
|
||||
<a href="/account">Multiplayer</a>
|
||||
</div>
|
||||
<div
|
||||
role="separator"
|
||||
class="-ml-[2px] mb-[2px] mt-[2px] h-[2px] w-[calc(100%+4px)] bg-[#423e41]"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
class="nav-btn flex items-center whitespace-nowrap hover:bg-[#1d1d1d] [&>*]:w-full [&>*]:px-4 [&>*]:py-2 [&>*]:text-left"
|
||||
>
|
||||
<button on:click={signOut}>Sign out</button>
|
||||
</div>
|
||||
{#if jsEnabled}
|
||||
<!-- <div
|
||||
role="button"
|
||||
class="nav-btn flex items-center whitespace-nowrap hover:bg-[#1d1d1d] [&>*]:w-full [&>*]:px-4 [&>*]:py-2 [&>*]:text-left"
|
||||
>
|
||||
<a href="/account">Multiplayer</a>
|
||||
</div>
|
||||
<div
|
||||
role="button"
|
||||
class="nav-btn flex items-center whitespace-nowrap hover:bg-[#1d1d1d] [&>*]:w-full [&>*]:px-4 [&>*]:py-2 [&>*]:text-left"
|
||||
></div>
|
||||
<div
|
||||
role="separator"
|
||||
class="-ml-[2px] mb-[2px] mt-[2px] h-[2px] w-[calc(100%+4px)] bg-[#423e41]"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
class="nav-btn flex items-center whitespace-nowrap hover:bg-[#1d1d1d] [&>*]:w-full [&>*]:px-4 [&>*]:py-2 [&>*]:text-left"
|
||||
>
|
||||
<button on:click={signOut}>Sign out</button>
|
||||
</div> -->
|
||||
{#each navItems as navItem}
|
||||
{#if navItem.type === "divider"}
|
||||
<div
|
||||
role="separator"
|
||||
class="-ml-[2px] mb-[2px] mt-[2px] h-[2px] w-[calc(100%+4px)] bg-[#423e41]"
|
||||
/>
|
||||
{:else}
|
||||
<div
|
||||
role="button"
|
||||
class="nav-btn flex items-center whitespace-nowrap hover:bg-[#1d1d1d] [&>*]:w-full [&>*]:px-4 [&>*]:py-2 [&>*]:text-left"
|
||||
>
|
||||
{#if navItem.type === "anchor"}
|
||||
<a href={navItem.href}>{navItem.name}</a>
|
||||
{:else}
|
||||
<button on:click={navItem.action}>{navItem.name}</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
|
|
|||
58
src/components/UserDisplay.svelte
Normal file
58
src/components/UserDisplay.svelte
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<script lang="ts">
|
||||
import type { SuyuUser } from "$lib/server/schema";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
delete: {};
|
||||
accept: {};
|
||||
}>();
|
||||
export let user: SuyuUser;
|
||||
export let accepted: boolean;
|
||||
export let sent: boolean;
|
||||
</script>
|
||||
|
||||
<!-- <div
|
||||
class="flex items-center gap-2 rounded-[16px] border-2 border-solid border-[#ffffff34] p-1 py-1"
|
||||
>
|
||||
<img
|
||||
src="{user.avatarUrl}&s=24"
|
||||
alt="{user.username}'s profile picture"
|
||||
width="24"
|
||||
height="24"
|
||||
class="ml-1 rounded-full"
|
||||
/>
|
||||
<p class="min-w-0 flex-grow overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{user.username}
|
||||
</p>
|
||||
{#if !accepted && !sent}
|
||||
<button class="button-sm h-8" on:click={() => dispatch("accept", {})}>Accept</button>
|
||||
{/if}
|
||||
<button class="button-sm h-8" on:click={() => dispatch("delete", {})}
|
||||
>{sent ? (accepted ? "Delete" : "Cancel") : "Delete"}</button
|
||||
>
|
||||
</div> -->
|
||||
|
||||
<div
|
||||
class="flex aspect-[1/1.5] w-full flex-col overflow-hidden rounded-3xl border-2 border-solid border-[#ffffff34] shadow-2xl shadow-black"
|
||||
>
|
||||
<div class="flex flex-grow items-center justify-center">
|
||||
<img
|
||||
src="{user.avatarUrl}&s=220"
|
||||
alt="{user.username}'s profile picture"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="flex h-[33%] w-full flex-shrink-0 flex-col border-t-2 border-t-[#ffffff34] px-3 py-2"
|
||||
>
|
||||
<h3 class="flex-grow text-center text-xl">{user.username}</h3>
|
||||
<div class="mb-[4px] flex justify-around">
|
||||
{#if !accepted && !sent}
|
||||
<button class="button-sm" on:click={() => dispatch("accept", {})}>Accept</button>
|
||||
{/if}
|
||||
<button class="button-sm" on:click={() => dispatch("delete", {})}
|
||||
>{sent ? (accepted ? "Delete" : "Cancel") : "Delete"}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -5,6 +5,7 @@ import type { Handle } from "@sveltejs/kit";
|
|||
import { WebSocketServer } from "ws";
|
||||
import { userRepo } from "$lib/server/repo";
|
||||
import { globalData } from "$lib/server/other";
|
||||
import type { Assets } from "./routes/api/webhooks/release/+server";
|
||||
|
||||
let server: WebSocketServer;
|
||||
|
||||
|
|
@ -39,12 +40,12 @@ async function setupGames() {
|
|||
|
||||
const runAllTheInitFunctions = async () => {
|
||||
if (!db.isInitialized) await db.initialize();
|
||||
if (!server)
|
||||
try {
|
||||
initServer();
|
||||
} catch {
|
||||
console.error("Could not initialize WebSocket server");
|
||||
}
|
||||
// if (!server)
|
||||
// try {
|
||||
// initServer();
|
||||
// } catch {
|
||||
// console.error("Could not initialize WebSocket server");
|
||||
// }
|
||||
if (globalData.games.length === 0) {
|
||||
await setupGames();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
import type {
|
||||
CreateAccountRequest,
|
||||
CreateAccountResponse,
|
||||
DeleteRelationshipRequest,
|
||||
DeleteRelationshipResponse,
|
||||
GetNotificationsResponse,
|
||||
GetRelationshipsResponse,
|
||||
GetUserResponse,
|
||||
LoginRequest,
|
||||
LoginResponse,
|
||||
SendFriendRequestRequest,
|
||||
SendFriendRequestResponse,
|
||||
} from "$types/api";
|
||||
|
||||
const apiUsers = {
|
||||
|
|
@ -24,8 +30,30 @@ const apiUsers = {
|
|||
},
|
||||
} as const;
|
||||
|
||||
const apiNotifications = {
|
||||
async getNotifications(): Promise<GetNotificationsResponse> {
|
||||
return await SuyuAPI.req("GET", "/api/notifications");
|
||||
},
|
||||
} as const;
|
||||
|
||||
const apiFriendship = {
|
||||
async sendFriendRequest(body: SendFriendRequestRequest): Promise<SendFriendRequestResponse> {
|
||||
return await SuyuAPI.req("POST", "/api/relationship", body);
|
||||
},
|
||||
|
||||
async getRelationships(): Promise<GetRelationshipsResponse> {
|
||||
return await SuyuAPI.req("GET", "/api/relationship");
|
||||
},
|
||||
|
||||
async deleteRelationship(body: DeleteRelationshipRequest): Promise<DeleteRelationshipResponse> {
|
||||
return await SuyuAPI.req("DELETE", "/api/relationship", body);
|
||||
},
|
||||
} as const;
|
||||
|
||||
export class SuyuAPI {
|
||||
static users = apiUsers;
|
||||
static notifications = apiNotifications;
|
||||
static friendship = apiFriendship;
|
||||
|
||||
static async req(method: string, path: string, body?: any) {
|
||||
const res = await fetch(path, {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { DataSource } from "typeorm";
|
||||
import { SuyuUser } from "../schema";
|
||||
import { Friendship, SuyuUser } from "../schema";
|
||||
|
||||
export const db = new DataSource({
|
||||
type: "better-sqlite3",
|
||||
database: "db.sqlite",
|
||||
entities: [SuyuUser],
|
||||
entities: [SuyuUser, Friendship],
|
||||
synchronize: true,
|
||||
subscribers: [],
|
||||
migrations: [],
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import type { Assets } from "../../../routes/api/webhooks/release/+server";
|
||||
|
||||
export interface SwitchGame {
|
||||
bannerUrl: string;
|
||||
category: string[];
|
||||
|
|
@ -23,6 +25,8 @@ export interface SwitchGame {
|
|||
size: number;
|
||||
// version: null;
|
||||
}
|
||||
|
||||
export const globalData: {
|
||||
games: SwitchGame[];
|
||||
} = { games: [] };
|
||||
assets: Assets | null;
|
||||
} = { games: [], assets: null };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { db } from "../db";
|
||||
import { FriendshipRequest, SuyuUser } from "../schema";
|
||||
import { Friendship, SuyuUser } from "../schema";
|
||||
|
||||
export const userRepo = db.getRepository(SuyuUser);
|
||||
export const friendshipRepo = db.getRepository(FriendshipRequest);
|
||||
export const friendshipRepo = db.getRepository(Friendship);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
import type { Role } from "$types/db";
|
||||
import { BaseEntity, Column, Entity, ManyToMany, OneToOne, PrimaryGeneratedColumn } from "typeorm";
|
||||
import {
|
||||
BaseEntity,
|
||||
Column,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
} from "typeorm";
|
||||
|
||||
@Entity()
|
||||
export class SuyuUser extends BaseEntity {
|
||||
|
|
@ -33,17 +43,59 @@ export class SuyuUser extends BaseEntity {
|
|||
})
|
||||
password: string;
|
||||
|
||||
@ManyToMany(() => SuyuUser)
|
||||
friends: SuyuUser[];
|
||||
@OneToMany(() => Friendship, (friendship) => friendship.from, {
|
||||
eager: true,
|
||||
})
|
||||
sentFriendRequests: Friendship[];
|
||||
|
||||
@OneToMany(() => Friendship, (friendship) => friendship.to, {
|
||||
eager: true,
|
||||
})
|
||||
receivedFriendRequests: Friendship[];
|
||||
|
||||
async getFriendRequests() {
|
||||
return await Friendship.find({
|
||||
where: {
|
||||
to: {
|
||||
id: this.id,
|
||||
},
|
||||
accepted: false,
|
||||
},
|
||||
loadEagerRelations: true,
|
||||
relations: ["from", "to"],
|
||||
});
|
||||
}
|
||||
|
||||
async getFriends() {
|
||||
const sent = await Friendship.find({
|
||||
where: {
|
||||
from: this,
|
||||
accepted: true,
|
||||
},
|
||||
});
|
||||
const received = await Friendship.find({
|
||||
where: {
|
||||
to: this,
|
||||
accepted: true,
|
||||
},
|
||||
});
|
||||
return sent.map((f) => f.to).concat(received.map((f) => f.from));
|
||||
}
|
||||
}
|
||||
|
||||
export class FriendshipRequest extends BaseEntity {
|
||||
@Entity()
|
||||
export class Friendship extends BaseEntity {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id: string;
|
||||
|
||||
@OneToOne(() => SuyuUser)
|
||||
@ManyToOne(() => SuyuUser, (user) => user.sentFriendRequests)
|
||||
@JoinColumn()
|
||||
from: SuyuUser;
|
||||
|
||||
@OneToOne(() => SuyuUser)
|
||||
@ManyToOne(() => SuyuUser, (user) => user.receivedFriendRequests)
|
||||
@JoinColumn()
|
||||
to: SuyuUser;
|
||||
|
||||
@Column("boolean")
|
||||
accepted: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ import type { Role } from "$types/db";
|
|||
import { PUBLIC_KEY } from "../secrets/secrets.json";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
export function json<T>(body: T): Response {
|
||||
export function json<T>(body: T, status?: number): Response {
|
||||
return new Response(JSON.stringify(body), {
|
||||
headers: {
|
||||
"content-type": "application/json;charset=UTF-8",
|
||||
},
|
||||
status: status || 200,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import type { IJwtData } from "$types/auth";
|
|||
import cookie from "cookie";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
export async function useAuth(request: Request | string): Promise<SuyuUser | null> {
|
||||
export async function useAuth(
|
||||
request: Request | string,
|
||||
eager?: boolean,
|
||||
): Promise<SuyuUser | null> {
|
||||
const cookies = cookie.parse(
|
||||
typeof request !== "string" ? request.headers.get("cookie") || "" : "",
|
||||
);
|
||||
|
|
@ -22,6 +25,8 @@ export async function useAuth(request: Request | string): Promise<SuyuUser | nul
|
|||
where: {
|
||||
apiKey: decoded.apiKey,
|
||||
},
|
||||
loadEagerRelations: eager || false,
|
||||
relations: eager ? ["sentFriendRequests", "receivedFriendRequests"] : [],
|
||||
});
|
||||
return user;
|
||||
}
|
||||
|
|
@ -29,6 +34,8 @@ export async function useAuth(request: Request | string): Promise<SuyuUser | nul
|
|||
where: {
|
||||
apiKey,
|
||||
},
|
||||
loadEagerRelations: eager || false,
|
||||
relations: eager ? ["sentFriendRequests", "receivedFriendRequests"] : [],
|
||||
});
|
||||
return user;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,10 +149,28 @@
|
|||
name: "GitLab",
|
||||
href: "https://gitlab.com/suyu-emu/suyu",
|
||||
},
|
||||
{
|
||||
name: $token || data.tokenCookie ? "Account" : "Sign up",
|
||||
href: $token || data.tokenCookie ? "/account" : "/signup",
|
||||
},
|
||||
// {
|
||||
// name: $token || data.tokenCookie ? "Account" : "Sign up",
|
||||
// href: $token || data.tokenCookie ? "/account" : "/signup",
|
||||
// },
|
||||
$token || data.tokenCookie
|
||||
? {
|
||||
name: "Account",
|
||||
href: "/account",
|
||||
}
|
||||
: {
|
||||
name: "Sign up",
|
||||
href: "/signup",
|
||||
},
|
||||
$token || data.tokenCookie
|
||||
? {
|
||||
name: "Log out",
|
||||
href: "/logout",
|
||||
}
|
||||
: {
|
||||
name: "Log in",
|
||||
href: "/login",
|
||||
},
|
||||
] as NavItem[];
|
||||
|
||||
$: {
|
||||
|
|
@ -227,13 +245,15 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
style="background: radial-gradient(50% 50%, rgba(255,0,0,0.05), transparent); z-index: -1; width: 800px ;height: 800px; position: fixed; top: -50%; left: calc(25% - 400px);"
|
||||
/>
|
||||
<div class="bg">
|
||||
<div
|
||||
style="background: radial-gradient(50% 50%, rgba(255,0,0,0.05), transparent); z-index: -1; width: 800px ;height: 800px; position: fixed; top: -50%; left: calc(25% - 400px);"
|
||||
/>
|
||||
|
||||
<div
|
||||
style="background: radial-gradient(50% 50%, rgba(0,128,255,0.05), transparent); z-index: -1; width: 800px ;height: 800px; position: fixed; top: -50%; right: calc(25% - 400px);"
|
||||
/>
|
||||
<div
|
||||
style="background: radial-gradient(50% 50%, rgba(0,128,255,0.05), transparent); z-index: -1; width: 800px ;height: 800px; position: fixed; top: -50%; right: calc(25% - 400px);"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<main
|
||||
class={`min-h-full w-full ${dropdownOpen || !dropdownCloseFinished ? "overflow-hidden" : ""}`}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { browser } from "$app/environment";
|
||||
import { transition } from "$lib/util/animation";
|
||||
import { onMount } from "svelte";
|
||||
import { onMount, tick } from "svelte";
|
||||
import type { PageData } from "./$types";
|
||||
import { page } from "$app/stores";
|
||||
import { writable } from "svelte/store";
|
||||
|
|
@ -29,10 +29,10 @@
|
|||
name: "Public Game Lobby",
|
||||
href: "/account/lobby",
|
||||
},
|
||||
{
|
||||
name: "Friends",
|
||||
href: "/account/friends",
|
||||
},
|
||||
// {
|
||||
// name: "Friends",
|
||||
// href: "/account/friends",
|
||||
// },
|
||||
];
|
||||
|
||||
function navClick(e: MouseEvent | HTMLAnchorElement) {
|
||||
|
|
@ -141,7 +141,7 @@
|
|||
|
||||
beforeNavigate(({ to }) => {
|
||||
if (!to) return;
|
||||
if (!to.url.pathname.startsWith("/account")) {
|
||||
if (!to.url.pathname.startsWith("/account") && to.url.hostname === location.hostname) {
|
||||
if (navBar.style.opacity === "0") return;
|
||||
navBar.animate(
|
||||
[
|
||||
|
|
@ -168,12 +168,11 @@
|
|||
}
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
setTimeout(() => {
|
||||
const items = Array.from(document.querySelectorAll(".navitem")) as HTMLAnchorElement[];
|
||||
const item = items.find((i) => new URL(i.href).pathname === data.url);
|
||||
navClick({ target: item } as unknown as MouseEvent);
|
||||
}, 10);
|
||||
onMount(async () => {
|
||||
await tick(); // if this doesn't work, do setTimeout on 10ms or so
|
||||
const items = Array.from(document.querySelectorAll(".navitem")) as HTMLAnchorElement[];
|
||||
const item = items.find((i) => new URL(i.href).pathname === data.url);
|
||||
navClick({ target: item } as unknown as MouseEvent);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,117 @@
|
|||
<!-- <script lang="ts">
|
||||
import { SuyuAPI } from "$lib/client/api";
|
||||
import type { GetNotificationsResponseSuccess } from "$types/api";
|
||||
import { onMount } from "svelte";
|
||||
import type { PageData } from "./$types";
|
||||
import { dev } from "$app/environment";
|
||||
import UserDisplay from "$components/UserDisplay.svelte";
|
||||
import { fly } from "svelte/transition";
|
||||
|
||||
let input = "";
|
||||
let relationships: GetNotificationsResponseSuccess["notifications"] = [];
|
||||
|
||||
async function sendFq() {
|
||||
const res = await SuyuAPI.friendship.sendFriendRequest({
|
||||
to: input,
|
||||
});
|
||||
if (!res.success) return alert(res.error);
|
||||
await refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const res = await SuyuAPI.friendship.getRelationships();
|
||||
if (!res.success) return alert(res.error);
|
||||
relationships = res.friendships || [];
|
||||
}
|
||||
|
||||
async function deleteRelationship(
|
||||
relationship: GetNotificationsResponseSuccess["notifications"][0],
|
||||
) {
|
||||
const res = await SuyuAPI.friendship.deleteRelationship({
|
||||
id: relationship.id,
|
||||
});
|
||||
if (!res.success) return alert(res.error);
|
||||
await refresh();
|
||||
}
|
||||
|
||||
async function acceptFq(relationship: GetNotificationsResponseSuccess["notifications"][0]) {
|
||||
const res = await SuyuAPI.friendship.sendFriendRequest({
|
||||
to: relationship.from?.username,
|
||||
});
|
||||
if (!res.success) return alert(res.error);
|
||||
await refresh();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
refresh();
|
||||
const interval = setInterval(refresh, dev ? 500 : 30000);
|
||||
return () => clearInterval(interval);
|
||||
});
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<div class="relative h-[calc(100vh-200px)] flex-col gap-6 overflow-hidden">
|
||||
<div
|
||||
class="relative flex w-full flex-col gap-6 overflow-hidden rounded-[2.25rem] bg-[#110d10] p-8 md:p-12"
|
||||
>
|
||||
<h2 class=" text-[36px] leading-[1.41] md:text-[48px] md:leading-[1.1]">Friends</h2>
|
||||
<p class="text-md text-wrap leading-relaxed text-[#4e4e4e]">
|
||||
TBD: madeline has no friends :( ~maddie/null
|
||||
<p class="text-md text-wrap leading-relaxed text-gray-400">
|
||||
As of right now, friends are just a way to keep track of who you know. In the future, we
|
||||
will add more features to this, such as inviting to rooms.
|
||||
</p>
|
||||
<div class="input-container text-md flex gap-4 text-wrap leading-relaxed">
|
||||
<input class="input !w-[250px]" placeholder="Username" type="text" bind:value={input} />
|
||||
<div class="flex gap-4">
|
||||
<button on:click={sendFq} class="cta-button">Send</button>
|
||||
<button on:click={refresh} class="button-sm">Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relationship-grid">
|
||||
{#each relationships as relationship}
|
||||
<div
|
||||
in:fly|global={{ duration: 360 }}
|
||||
out:fly|global={{
|
||||
duration: 360,
|
||||
}}
|
||||
>
|
||||
<UserDisplay
|
||||
sent={relationship.to.id !== data.user.id}
|
||||
accepted={relationship.accepted}
|
||||
user={relationship.to.id === data.user.id
|
||||
? relationship.from
|
||||
: relationship.to}
|
||||
on:delete={() => deleteRelationship(relationship)}
|
||||
on:accept={() => acceptFq(relationship)}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.relationship-grid {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.relationship-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 650px) {
|
||||
.relationship-grid {
|
||||
grid-template-columns: 2fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.input-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style> -->
|
||||
|
|
|
|||
18
src/routes/api/notifications/+server.ts
Normal file
18
src/routes/api/notifications/+server.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// import { json } from "$lib/server/util/index.js";
|
||||
// import { useAuth } from "$lib/util/api/index.js";
|
||||
// import type { GetNotificationsResponse } from "$types/api";
|
||||
|
||||
// export async function GET({ request }) {
|
||||
// const user = await useAuth(request);
|
||||
// if (!user)
|
||||
// return json<GetNotificationsResponse>({
|
||||
// success: false,
|
||||
// error: "Not logged in",
|
||||
// });
|
||||
// const requests = await user.getFriendRequests();
|
||||
// console.log(requests);
|
||||
// return json<GetNotificationsResponse>({
|
||||
// success: true,
|
||||
// notifications: requests,
|
||||
// });
|
||||
// }
|
||||
149
src/routes/api/relationship/+server.ts
Normal file
149
src/routes/api/relationship/+server.ts
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
// import { friendshipRepo, userRepo } from "$lib/server/repo";
|
||||
// import { json } from "$lib/server/util/index.js";
|
||||
// import { useAuth } from "$lib/util/api/index.js";
|
||||
// import type {
|
||||
// GetRelationshipsResponse,
|
||||
// DeleteAccountResponse,
|
||||
// DeleteRelationshipRequest,
|
||||
// DeleteRelationshipResponse,
|
||||
// SendFriendRequestRequest,
|
||||
// } from "$types/api.js";
|
||||
|
||||
// export async function POST({ request }) {
|
||||
// const body: SendFriendRequestRequest = await request.json();
|
||||
// const user = await useAuth(request, true);
|
||||
// if (!user)
|
||||
// return json({
|
||||
// success: false,
|
||||
// error: "Not logged in",
|
||||
// });
|
||||
// const to = await userRepo.findOne({
|
||||
// where: {
|
||||
// username: body.to,
|
||||
// },
|
||||
// });
|
||||
// if (!to)
|
||||
// return json({
|
||||
// success: false,
|
||||
// error: "User not found",
|
||||
// });
|
||||
// if (to.id === user.id)
|
||||
// return json({
|
||||
// success: false,
|
||||
// error: "Cannot send friend request to self",
|
||||
// });
|
||||
// if (user.sentFriendRequests.some((frq) => frq.to.id === to.id))
|
||||
// return json({
|
||||
// success: false,
|
||||
// error: "Friend request already sent",
|
||||
// });
|
||||
// const foundFrq = await friendshipRepo.findOne({
|
||||
// where: {
|
||||
// to: {
|
||||
// id: user.id,
|
||||
// },
|
||||
// from: {
|
||||
// id: to.id,
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// if (foundFrq) {
|
||||
// foundFrq.accepted = true;
|
||||
// await friendshipRepo.save(foundFrq);
|
||||
// return json({
|
||||
// success: true,
|
||||
// });
|
||||
// }
|
||||
// const frq = friendshipRepo.create({
|
||||
// to,
|
||||
// from: user,
|
||||
// accepted: false,
|
||||
// });
|
||||
// await friendshipRepo.save(frq);
|
||||
// return json({
|
||||
// success: true,
|
||||
// });
|
||||
// }
|
||||
|
||||
// export async function DELETE({ request }) {
|
||||
// const body: DeleteRelationshipRequest = await request.json();
|
||||
// const user = await useAuth(request, true);
|
||||
// if (!user)
|
||||
// return json<DeleteRelationshipResponse>(
|
||||
// {
|
||||
// success: false,
|
||||
// error: "Not logged in",
|
||||
// },
|
||||
// 401,
|
||||
// );
|
||||
// const relationship = await friendshipRepo.findOne({
|
||||
// where: {
|
||||
// id: body.id,
|
||||
// },
|
||||
// relations: ["to", "from"],
|
||||
// });
|
||||
// if (!relationship)
|
||||
// return json<DeleteRelationshipResponse>(
|
||||
// {
|
||||
// success: false,
|
||||
// error: "Relationship not found",
|
||||
// },
|
||||
// 404,
|
||||
// );
|
||||
// if (relationship.to.id !== user.id && relationship.from.id !== user.id)
|
||||
// return json<DeleteRelationshipResponse>(
|
||||
// {
|
||||
// success: false,
|
||||
// error: "Not authorized",
|
||||
// },
|
||||
// 403,
|
||||
// );
|
||||
// await friendshipRepo.remove(relationship);
|
||||
// return json<DeleteRelationshipResponse>({
|
||||
// success: true,
|
||||
// friendships: user.sentFriendRequests.concat(user.receivedFriendRequests),
|
||||
// });
|
||||
// }
|
||||
|
||||
// export async function GET({ request }) {
|
||||
// const auth = await useAuth(request, true);
|
||||
// if (!auth) {
|
||||
// return json<GetRelationshipsResponse>(
|
||||
// {
|
||||
// success: false,
|
||||
// error: "Not logged in",
|
||||
// },
|
||||
// 401,
|
||||
// );
|
||||
// }
|
||||
|
||||
// const user = await userRepo.findOne({
|
||||
// where: {
|
||||
// id: auth.id,
|
||||
// },
|
||||
// relations: [
|
||||
// "sentFriendRequests",
|
||||
// "sentFriendRequests.from",
|
||||
// "sentFriendRequests.to",
|
||||
// "receivedFriendRequests",
|
||||
// "receivedFriendRequests.from",
|
||||
// "receivedFriendRequests.to",
|
||||
// ],
|
||||
// loadEagerRelations: true,
|
||||
// });
|
||||
|
||||
// if (!user) {
|
||||
// return json<GetRelationshipsResponse>(
|
||||
// {
|
||||
// success: false,
|
||||
// error: "you don't exist?",
|
||||
// },
|
||||
// 404,
|
||||
// );
|
||||
// }
|
||||
|
||||
// return json<GetRelationshipsResponse>({
|
||||
// success: true,
|
||||
// friendships: user.sentFriendRequests.concat(user.receivedFriendRequests),
|
||||
// });
|
||||
// }
|
||||
|
|
@ -3,14 +3,7 @@ import { RateLimiter, json } from "$lib/server/util/index.js";
|
|||
import type { LoginResponse, LoginRequest } from "$types/api";
|
||||
import bcrypt from "bcrypt";
|
||||
|
||||
const rateLimit = new RateLimiter();
|
||||
|
||||
export async function POST({ request, getClientAddress }) {
|
||||
if (rateLimit.isLimited(getClientAddress()))
|
||||
return json<LoginResponse>({
|
||||
success: false,
|
||||
error: "rate limited",
|
||||
});
|
||||
const body: LoginRequest = await request.json();
|
||||
if (
|
||||
!body.email ||
|
||||
|
|
@ -29,6 +22,7 @@ export async function POST({ request, getClientAddress }) {
|
|||
email: body.email,
|
||||
},
|
||||
select: ["password", "apiKey"],
|
||||
loadEagerRelations: false,
|
||||
});
|
||||
if (!user)
|
||||
return json<LoginResponse>({
|
||||
|
|
|
|||
68
src/routes/api/webhooks/release/+server.ts
Normal file
68
src/routes/api/webhooks/release/+server.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import { json } from "$lib/server/util/index.js";
|
||||
import { GITLAB_WEBHOOK_TOKEN } from "$env/static/private";
|
||||
|
||||
export interface Release {
|
||||
id: number;
|
||||
created_at: string;
|
||||
description: string;
|
||||
name: string;
|
||||
released_at: string;
|
||||
tag: string;
|
||||
object_kind: string;
|
||||
project: Project;
|
||||
url: string;
|
||||
action: string;
|
||||
assets: Assets;
|
||||
commit: Commit;
|
||||
}
|
||||
|
||||
export interface Assets {
|
||||
count: number;
|
||||
links: Array<(string | null)[]>;
|
||||
sources: Array<(string | null)[]>;
|
||||
}
|
||||
|
||||
export interface Commit {
|
||||
id: string;
|
||||
message: string;
|
||||
title: string;
|
||||
timestamp: Date;
|
||||
url: string;
|
||||
author: Author;
|
||||
}
|
||||
|
||||
export interface Author {
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
id: number;
|
||||
name: string;
|
||||
description: null;
|
||||
web_url: string;
|
||||
avatar_url: null;
|
||||
git_ssh_url: string;
|
||||
git_http_url: string;
|
||||
namespace: string;
|
||||
visibility_level: number;
|
||||
path_with_namespace: string;
|
||||
default_branch: string;
|
||||
ci_config_path: string;
|
||||
homepage: string;
|
||||
url: string;
|
||||
ssh_url: string;
|
||||
http_url: string;
|
||||
}
|
||||
|
||||
export async function POST({ request }) {
|
||||
const body: Release = await request.json();
|
||||
const headers = request.headers;
|
||||
if (headers.get("X-Gitlab-Token") !== GITLAB_WEBHOOK_TOKEN)
|
||||
return json({
|
||||
success: false,
|
||||
message: "Nice try!",
|
||||
});
|
||||
|
||||
return json({ success: true, message: "Ok, you win. For now." });
|
||||
}
|
||||
37
src/types/api.d.ts
vendored
37
src/types/api.d.ts
vendored
|
|
@ -1,6 +1,6 @@
|
|||
/* api types */
|
||||
|
||||
import type { SuyuUser } from "$lib/server/schema";
|
||||
import type { Friendship, SuyuUser } from "$lib/server/schema";
|
||||
|
||||
export interface GenericFailureResponse {
|
||||
success: false;
|
||||
|
|
@ -45,3 +45,38 @@ export interface LoginResponseSuccess {
|
|||
}
|
||||
|
||||
export type LoginResponse = LoginResponseSuccess | GenericFailureResponse;
|
||||
|
||||
export interface GetNotificationsResponseSuccess {
|
||||
success: true;
|
||||
notifications: Friendship[];
|
||||
}
|
||||
|
||||
export type GetNotificationsResponse = GetNotificationsResponseSuccess | GenericFailureResponse;
|
||||
|
||||
export interface SendFriendRequestRequest {
|
||||
to: string;
|
||||
}
|
||||
|
||||
export interface SendFriendRequestResponseSuccess {
|
||||
success: true;
|
||||
}
|
||||
|
||||
export type SendFriendRequestResponse = SendFriendRequestResponseSuccess | GenericFailureResponse;
|
||||
|
||||
export interface DeleteRelationshipRequest {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface DeleteRelationshipResponseSuccess {
|
||||
success: true;
|
||||
friendships: Friendship[];
|
||||
}
|
||||
|
||||
export type DeleteRelationshipResponse = DeleteRelationshipResponseSuccess | GenericFailureResponse;
|
||||
|
||||
export interface GetRelationshipsResponseSuccess {
|
||||
success: true;
|
||||
friendships: Friendship[];
|
||||
}
|
||||
|
||||
export type GetRelationshipsResponse = GetRelationshipsResponseSuccess | GenericFailureResponse;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue