This commit is contained in:
Korbs 2024-07-24 15:53:15 -04:00
parent 6f3a08867f
commit 36d554c16b
30 changed files with 5790 additions and 650 deletions

View file

@ -7,14 +7,21 @@ SERVER_DOMAIN="https://example.org"
# Default Instances and Options
## Set the instances you want to use.
## Can be overriden by the user in their preferences.
### Supabase Server
SUPABASE_URL="http://supa.example.com"
SUPABASE_ANON_KEY="anon_key"
### Invidious
DEFAULT_MEDIA_PROXY="https://yt.sudovanilla.org" # Invidious (YouTube)
DEFAULT_MEDIA_DATA_PROXY="https://yt.sudovanilla.org" # Invidious (YouTube)
### SafeTwitch
DEFAULT_STREAM_PROXY="https://twitch-backend.sudovanilla.org" # SafeTwitch (Twitch)
DEFAULT_STREAM_DATA_PROXY="https://twitch-backend.sudovanilla.org" # SafeTwitch (Twitch)
### Image Proxy
DEFAULT_IMAGE_PROXY="https://ipx.sudovanilla.org"
### Options
DEFAULT_PLAYER="Zorn"
# Important Information
# Source Code Information
## If you've modified the source code, please publish
## your changes and list the repo here.
MODIFIED="false"

3
.gitignore vendored
View file

@ -25,3 +25,6 @@ pnpm-debug.log*
/src/pages/en/
/src/pages/jp/
/src/pages/ru/
# other
supabase/

View file

@ -24,27 +24,25 @@ ___
- [ ] Allow 4K
- [ ] Allow 8K
- [ ] Account System (Based on [Account System Demo](https://ark.sudovanilla.org/MinPluto/Account-System-Demo))
- [ ] Use Supabase Library
- [x] Use Supabase Library
- [ ] Create Pages:
- [ ] Subscription Feed
- [ ] History (Maybe, maybe not)
- [ ] Login
- [ ] Register
- [ ] Account
- [x] Login
- [x] Register
- [x] Account
- [ ] Preferences
- [ ] Delete
- [ ] Anomymous Account Creation
- [ ] Email Confirmation Code
- [x] Email Confirmation Code
- [ ] Ability to:
- [ ] Update Data
- [ ] Avatar (Reminder: Proxy avatar URL)
- [ ] Username
- [x] Username
- [ ] Email
- [ ] Pasword
- [ ] Delete Account
- [ ] API
- [ ] `/api/update/avatar` (Reminder: Proxy avatar URL)
- [ ] `/api/update/name`
- [x] `/api/update/name`
- [ ] `/api/update/email`
- [ ] `/api/update/password`
- [ ] `/api/update/preference/ui/theme`
@ -57,11 +55,11 @@ ___
- [ ] `/api/update/preference/instance/invidious/data`
- [ ] `/api/update/preference/instance/safetwitch/media`
- [ ] `/api/update/preference/instance/safetwitch/data`
- [ ] `/api/auth/login`
- [ ] `/api/auth/register`
- [x] `/api/auth/login`
- [x] `/api/auth/register`
- [ ] `/api/auth/delete`
- [ ] `/api/auth/confirm`
- [ ] `/api/auth/logout`
- [x] `/api/auth/confirm`
- [x] `/api/auth/logout`
- [ ] `/api/anon/create`
- [ ] `/api/anon/delete`
- [ ] `/api/anon/signout`

View file

@ -1,9 +1,10 @@
import { defineConfig } from 'astro/config'
import node from '@astrojs/node'
import vue from '@astrojs/vue'
import astroI18next from "astro-i18next"
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import vue from '@astrojs/vue';
import astroI18next from "astro-i18next";
import mdx from '@astrojs/mdx';
// https://astro.build/config
export default defineConfig({
// Project Structure
publicDir: './src/public/',
@ -21,8 +22,16 @@ export default defineConfig({
// Use Server-Side Rendering
output: 'server',
adapter: node({
mode: 'standalone',
mode: 'standalone'
}),
// Vite
vite: {
server: {
hmr: false // Auto Reload
}
},
// Others
devToolbar: {enabled: false},
})
devToolbar: {
enabled: false
}
});

BIN
bun.lockb

Binary file not shown.

5746
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -33,11 +33,11 @@
"@astrojs/vue": "^4.5.0",
"@iconoir/vue": "^7.7.0",
"@openpanel/sdk": "^0.0.9-beta",
"astro": "^4.11.5",
"@supabase/supabase-js": "^2.44.4",
"astro": "^4.12.1",
"astro-analytics": "^2.7.0",
"astro-i18next": "^1.0.0-beta.21",
"astro-useragent": "^4.0.2",
"express": "^4.19.2",
"rss-to-json": "^2.1.1",
"undici": "^6.19.2"
},

View file

@ -19,9 +19,6 @@ if (Astro.cookies.get("Telemtry") === undefined) {
Astro.cookies.set("Telemtry", "Disabled", {path: "/",sameSite: 'strict'})
}
//// Check what language the user has set it to and switch to it
// Properties
const {
Title,
@ -99,5 +96,4 @@ if (Astro.url.href.match('watch')) {
</head>
<Analytics/>
<script type="module" is:inline>
</script>
<script is:inline src="./assets/vendor/preline/preline.js"></script>

5
src/env.d.ts vendored
View file

@ -1 +1,6 @@
/// <reference types="astro/client" />
declare namespace App {
interface Locals {
email: string;
}
}

115
src/layouts/Settings.astro Normal file
View file

@ -0,0 +1,115 @@
---
// Properties
const { Title, Description } = Astro.props
// Components
import Head from '@components/global/Head.astro'
import MobileNav from '@components/global/MobileNav.astro'
import Sidebar from '@components/global/Sidebar.astro'
import Footer from '@components/global/Footer.astro'
// Styles
import '@styles/index.scss'
import '@styles/mobile.scss'
import { CodeBrackets, EditPencil, EvPlugXmark, FloppyDisk, HelpCircle, InfoCircle, LogOut, Play, PrivacyPolicy, ProfileCircle, Server, UnionAlt, XmarkCircle } from '@iconoir/vue'
---
<Head Title={Title} Description={Description}/>
<MobileNav/>
<Sidebar/>
<div class="content settings-area">
<div class="settings-sidebar">
<div>
<h2>General</h2>
<a id="sidebar-disable" href="#"><EditPencil/> Customization</a>
<a id="sidebar-disable" href="#"><Play/> Video Player</a>
<a id="sidebar-disable" href="#"><CodeBrackets/> CSS/JS</a>
<h2>You</h2>
<a href="#"><ProfileCircle/> Account</a>
<h2>Advanced</h2>
<a id="sidebar-disable" href="#"><EvPlugXmark/> Reset</a>
<a id="sidebar-disable" href="#"><FloppyDisk/> Backup & Restore</a>
<a id="sidebar-disable" href="#"><XmarkCircle/> Delete Account</a>
</div>
<div>
<a id="sidebar-disable" href="#"><HelpCircle/> Help</a>
<a href="#"><UnionAlt/> Telemtry</a>
<a href="#"><Server/> Instance</a>
<a href="#"><PrivacyPolicy/> Privacy Policy</a>
<hr/>
<a href="#"><LogOut/> Log Out</a>
</div>
</div>
<div class="settings-content">
<slot/>
</div>
</div>
<Footer/>
<style is:inline>a[href="/settings"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style lang="scss">
.settings-area {
display: flex;
gap: 24px;
position: relative;
flex-wrap: wrap;
align-items: stretch;
min-height: 100vh;
.settings-content {
max-width: 750px;
margin: 0px auto;
}
.settings-sidebar {
background: #161616;
width: 180px;
color: white;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 0px 12px;
div {
position: sticky;
}
div:nth-child(1) {
position: sticky;
top: 0px;
}
div:nth-child(2) {
position: sticky;
bottom: 0px;
}
h2 {
font-size: 16px;
padding-left: 12px;
}
a {
color: white;
text-decoration: none;
margin-bottom: 6px;
border-radius: 6px;
padding: 4px 12px;
background: transparent;
border: 2px transparent solid;
display: flex;
align-items: center;
gap: 14px;
font-size: 14px;
&:hover {
background: rgb(255 255 255 / 05%);
border: 2px rgba(255, 255, 255, 0.05) solid;
}
svg {
width: 18px;
}
&#sidebar-disable {
filter: brightness(0.4);
cursor: not-allowed;
&:hover {
background: transparent;
border: 2px transparent solid;
}
}
}
}
}
</style>

14
src/library/supabase.ts Normal file
View file

@ -0,0 +1,14 @@
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
import.meta.env.SUPABASE_URL,
import.meta.env.SUPABASE_ANON_KEY,
{
auth: {
flowType: "pkce",
autoRefreshToken: false,
detectSessionInUrl: false,
persistSession: true,
},
},
);

View file

@ -0,0 +1,53 @@
---
import i18next,{ t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import '@styles/form.scss'
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<hr/>
<img src="/images/backgrounds/1.webp"/>
</div>
<form action="/api/auth/confirm" method="post">
<div class="form-field">
<label>Email</label>
<input name="email" type="email" autocomplete="off" required/>
</div>
<div class="form-field">
<label>Code</label>
<input name="code" type="text" autocomplete="off" required/>
</div>
<div class="form-actions">
<div></div>
<div>
<span></span>
<button type="submit">Confirm</button>
</div>
</div>
</form>
</Base>
<style is:global>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
}
.force-center {
text-align: center;
padding-top: 10%;
}
.search-bar {
display: none !important;
}
</style>

View file

@ -0,0 +1,59 @@
---
// i18n
import i18next,{ t, changeLanguage } from "i18next";
import Settings from "@layouts/Settings.astro";
// Supabase Data
import { supabase } from "@library/supabase"
const { data: { user } } = await supabase.auth.getUser()
const ID = user?.id
const Name = user?.user_metadata.name
const Email = user?.email
// Styles
import '@styles/form.scss'
---
<Settings Title="Account" Description="Manage your account">
<p>Account ID: {ID}</p>
<form action="/api/update/name" method="post">
<div class="form-field">
<label>Username</label>
<input name="name" type="text" autocomplete="off" value={Name} required/>
</div>
<div class="form-actions"><div></div><div><span></span><button type="submit">Update</button></div></div>
</form>
<form>
<div class="form-field">
<label>Email</label>
<input name="email" type="email" autocomplete="off" value={Email} readonly/>
</div>
<div class="form-actions"><div></div><div><span></span><button type="submit">Update</button></div></div>
</form>
<form onkeypress="CheckPasswordConfirmation()">
<div class="form-field">
<label>Password</label>
<input id="password" name="password" type="password" autocomplete="off" required/>
</div>
<div class="form-field">
<label>Confirm Password</label>
<input id="confirm_password" name="password" type="password" autocomplete="off" required/>
</div>
<div class="form-actions">
<div>
<a href="#">Forgot Password</a>
</div>
<div><span></span><button type="submit">Update</button></div>
</div>
</form>
</Settings>
<script is:inline>
function CheckPasswordConfirmation() {
if (document.getElementById('confirm_password').value !== document.getElementById('password').value) {
document.getElementById('confirm_password').setCustomValidity('Password Must be Matching.');
} else {
document.getElementById('confirm_password').setCustomValidity('');
}
}
</script>

View file

@ -0,0 +1,20 @@
import type { APIRoute } from "astro"
import { supabase } from "@library/supabase"
export const POST: APIRoute = async ({ request, redirect }) => {
const formData = await request.formData()
const EMAIL = formData.get("email")?.toString()
const OTP = formData.get("code")?.toString()
if (!OTP) {
return new Response("Code required", { status: 400 })
}
const { error } = await supabase.auth.verifyOtp({type: 'email', token: OTP, email: EMAIL})
if (error) {
return new Response(error.message, { status: 500 })
}
return redirect("/login?=confirmed")
}

View file

@ -0,0 +1,35 @@
import type { APIRoute } from "astro"
import { supabase } from "@library/supabase"
export const POST: APIRoute = async ({ request, cookies, redirect }) => {
const formData = await request.formData()
const email = formData.get("email")?.toString()
const password = formData.get("password")?.toString()
if (!email || !password) {
return new Response("Email and password are required", { status: 400 })
}
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) {
return new Response(error.message, { status: 500 })
}
const { access_token, refresh_token } = data.session
cookies.set("sb-access-token", access_token, {
sameSite: "strict",
path: "/",
secure: true,
})
cookies.set("sb-refresh-token", refresh_token, {
sameSite: "strict",
path: "/",
secure: true,
})
return redirect("/")
}

View file

@ -0,0 +1,15 @@
import type { APIRoute } from "astro"
import { supabase } from "@library/supabase"
import type { Provider } from "@supabase/supabase-js"
export const GET: APIRoute = async ({ cookies, redirect }) => {
if(cookies.get('anonymous-session')) {
return redirect('/account/anon/end')
} else {
cookies.delete("sb-access-token", { path: "/" })
cookies.delete("sb-refresh-token", { path: "/" })
const { error } = await supabase.auth.signOut()
}
return redirect("/")
}

View file

@ -0,0 +1,40 @@
import type { APIRoute } from "astro"
import { supabase } from "@library/supabase"
export const POST: APIRoute = async ({ request, redirect }) => {
const formData = await request.formData()
const name = formData.get("username")?.toString()
const email = formData.get("email")?.toString()
const password = formData.get("password")?.toString()
if (!email || !password || !name) {
return new Response("Email and password are required", { status: 400 })
}
const { error } = await supabase.auth.signUp({
options: {
emailRedirectTo: "http://localhost:1930/?=welcome",
data: {
name: name,
ui_theme: "Default",
ui_color: "Default",
ui_zen: "false",
ui_sidebar_size: "Normal",
invidous_data: "https://yt.sudovanilla.org",
invidous_media: "https://yt.sudovanilla.org",
safetwitch_data: "https://twitch.sudovanilla.org",
safetwitch_media: "https://twitch.sudovanilla.org",
image_proxy: "https://ipx.sudovanilla.org",
player_type: "Zorn"
}
},
email,
password,
})
if (error) {
return new Response(error.message, { status: 500 })
}
return redirect("/account/confirm-your-email")
}

View file

@ -0,0 +1,22 @@
import type { APIRoute } from "astro"
import { supabase } from "@library/supabase"
export const POST: APIRoute = async ({ request, redirect }) => {
const formData = await request.formData()
const NewEmail = formData.get("email")?.toString()
if (!NewEmail) {
return new Response("Error.", { status: 400 })
}
const { error } = await supabase.auth.resend({
type: 'email_change',
email: NewEmail
})
if (error) {
return new Response(error.message, { status: 500 })
}
return redirect("/account?=CheckEmail")
}

View file

@ -0,0 +1,21 @@
import type { APIRoute } from "astro"
import { supabase } from "@library/supabase"
export const POST: APIRoute = async ({ request, redirect }) => {
const formData = await request.formData()
const name = formData.get("name")?.toString()
if (!name) {
return new Response("Error.", { status: 400 })
}
const { error } = await supabase.auth.updateUser({
data: {name: name}
})
if (error) {
return new Response(error.message, { status: 500 })
}
return redirect("/account?=NameUpdated")
}

View file

@ -3,7 +3,7 @@ import { t, changeLanguage } from "i18next";
import Embed from "@layouts/Embed.astro";
import "@styles/video.scss";
// Configuration
import { DEFAULT_MEDIA_DATA_PROXY, DEFAULT_MEDIA_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from '@utils/GetConfig'
import { DEFAULT_MEDIA_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from '@utils/GetConfig'
// Fetch
const SWV = Astro.url.href.split("embed/").pop();

View file

@ -78,6 +78,8 @@ const GamingSplit = GamingData.slice(0, 1)
</div>
</Base>
<style is:inline>.sidebar-top-end a[href="/"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style>
img[src="/images/backgrounds/1.webp"] {
position: fixed;

View file

@ -1,5 +1,5 @@
---
layout: '@layouts/Markdown.astro'
layout: '@layouts/Settings.astro'
---
import {

58
src/pages/login.astro Normal file
View file

@ -0,0 +1,58 @@
---
import i18next,{ t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import '@styles/form.scss'
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<hr/>
<img src="/images/backgrounds/1.webp"/>
</div>
<form action="/api/auth/login" method="post">
<div class="form-field">
<label>Email</label>
<input name="email" type="email" autocomplete="off" required/>
</div>
<div class="form-field">
<label>Password</label>
<input name="password" type="password" autocomplete="off" required/>
</div>
<div class="form-actions">
<div>
<a aria-disabled="true" href="#">Forgot Password</a>
</div>
<div>
<button onclick="location.href = '/register/'">Create Account</button>
<button type="submit">Login</button>
</div>
</div>
</form>
</Base>
<style is:inline>a[href="/login"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style is:global>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
}
.force-center {
text-align: center;
padding-top: 10%;
}
.search-bar {
display: none !important;
}
form {
max-width: 500px !important;
}
</style>

View file

@ -1,5 +1,5 @@
---
layout: "@layouts/Markdown.astro"
layout: "@layouts/Settings.astro"
---
import {

66
src/pages/register.astro Normal file
View file

@ -0,0 +1,66 @@
---
import i18next,{ t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import '@styles/form.scss'
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<hr/>
<img src="/images/backgrounds/1.webp"/>
</div>
<form action="/api/auth/register" method="post">
<div class="form-field">
<label>Username</label>
<input name="username" type="text" autocomplete="off" required/>
</div>
<div class="form-field">
<label>Email</label>
<input name="email" type="email" autocomplete="off" required/>
</div>
<div style="display: flex; justify-content: space-between; gap: 6px;">
<div style="width: 100%;" class="form-field">
<label>Password</label>
<input name="password" type="password" autocomplete="off" required/>
</div>
</div>
<div class="form-actions">
<div>
<a href="/privacy">Privacy Policy</a>
</div>
<div>
<button onclick="location.href = '/login'">Login</button>
<button type="submit">Register</button>
</div>
</div>
</form>
</Base>
<style is:global>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
z-index: -1;
opacity: 0.5;
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
}
.force-center {
text-align: center;
padding-top: 10%;
}
.search-bar {
display: none !important;
}
form {
max-width: 500px !important;
}
</style>

View file

@ -28,12 +28,37 @@
"STATUS": "Status",
"FORUM": "Forum",
"SOURCE_CODE": "Source Code"
},
"ACCOUNT": {
"CUSTOMIZATION": "",
"VIDEO_PLAYER": "",
"CSS/JS": "",
"ACCOUNT": "",
"RESET": "",
"BACKUP_RESTORE": "",
"DELETE_ACCOUNT": "",
"HEADERS": {
"SETTINGS": "Settings",
"YOU": "You",
"ADVANCE": "Advance"
}
}
},
"HEADER": {
"SEARCH": "Search",
"FEEDBACK": "Feedback"
},
"FORM": {
"EMAIL": "Email",
"PASSWORD": "Password",
"USERNAME": "Username",
"CREATE_ACCOUNT": "Create Account",
"FORGOT_PASSWORD": "Forgot Password",
"LOGIN": "Login",
"REGISTER": "Register",
"UPDATE": "Update",
"SUBMIT": "Submit"
},
"TELEMTRY": {
"OPTIN": "Opt-In",
"OPTOUT": "Opt-Out"

59
src/styles/form.scss Normal file
View file

@ -0,0 +1,59 @@
form {
margin: auto;
display: grid;
gap: 12px;
margin-bottom: 12px;
.form-field {
display: flex;
flex-direction: column;
gap: 6px;
background: #161616;
color: white;
border: 1px #2d2d2d solid;
border-radius: 4px;
padding: 6px 12px;
position: relative;
label {
font-size: 12px;
color: rgb(145, 145, 145);
z-index: 1;
pointer-events: none;
}
input {
background: transparent;
border: none;
color: white;
padding: 32px 12px 12px 12px;
margin: -26px -12px -6px -12px;
border-radius: 4px;
&:hover {
background: rgb(32, 32, 32);
}
&:focus {
background: rgb(48, 48, 48);
outline: none;
}
}
}
.form-actions {
display: flex;
align-items: center;
justify-content: space-between;
button {
background: white;
color: black;
padding: 6px 12px;
border-radius: 3rem;
border: 1px white solid;
cursor: pointer;
&:hover {
filter: brightness(0.8);
}
&:nth-child(1) {
border: 1px white solid;
background: transparent;
color: white;
}
}
}
}

View file

@ -6,7 +6,7 @@ body {
top: 0px;
right: 0px;
margin: auto;
width: calc(100% - 248px);
width: calc(100% - 204px);
}
a {

View file

@ -1,10 +1,12 @@
{
"extends": "astro/tsconfigs/base",
"compilerOptions": {
"baseUrl": ".",
"allowSyntheticDefaultImports": true,
"paths": {
"@components/*": ["src/components/*"],
"@layouts/*": ["src/layouts/*"],
"@library/*": ["src/library/*"],
"@root/*": ["*"],
"@styles/*": ["src/styles/*"],
"@utils/*": ["src/utilities/*"]