Merge branch 'master' of github.com:pockethost/pockethost

This commit is contained in:
Ben Allfree 2023-12-08 14:55:18 -08:00
commit fa1961fbe6
22 changed files with 211 additions and 272 deletions

View File

@ -20,7 +20,7 @@ This app uses [Svelte Stores](https://svelte.dev/docs#run-time-svelte-store) to
### Derived User Values
There are additional derived values that are useful for showing and hiding components across the site. The first one is `isUserLoggedIn`. This one will return a true or false depending on the state of the logged in user. It is dependent on the `email` property in the Pocketbase response.
There are additional derived values that are useful for showing and hiding components across the site. The first one is `isUserLoggedIn`. This one will return a true or false depending on the state of the logged-in user. It is dependent on the `email` property in the Pocketbase response.
The second derived value is `isUserVerified`. This will return a true or false boolean depending on if their Pocketbase account has been verified via the email they got when they initially registered.

View File

@ -1,4 +1,5 @@
<!doctype html>
<!--suppress ALL -->
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8" />

View File

@ -1,36 +1,51 @@
<script lang="ts">
enum AlertTypes {
Primary = 'primary',
Secondary = 'secondary',
Success = 'success',
Danger = 'danger',
Warning = 'warning',
Info = 'info',
Light = 'light',
Dark = 'dark',
import { slide } from 'svelte/transition'
// https://daisyui.com/components/alert/
type AlertTypes = 'default' | 'info' | 'success' | 'warning' | 'error'
export let message: string = ''
export let type: AlertTypes
export let additionalClasses: string = ''
// Set up the default alert classes and icon
let alertTypeClass = ''
let alertTypeIcon = `<i class="fa-regular fa-circle-info"></i>`
if (type === 'default') {
alertTypeClass = ''
alertTypeIcon = `<i class="fa-regular fa-circle-info"></i>`
}
export let title: string = ''
export let text: string = ''
export let icon: string = ''
export let alertType: AlertTypes = AlertTypes.Warning
if (type === 'info') {
alertTypeClass = 'alert-info'
alertTypeIcon = `<i class="fa-regular fa-circle-info"></i>`
}
if (type === 'success') {
alertTypeClass = 'alert-success'
alertTypeIcon = `<i class="fa-regular fa-circle-check"></i>`
}
if (type === 'warning') {
alertTypeClass = 'alert-warning'
alertTypeIcon = `<i class="fa-regular fa-triangle-exclamation"></i>`
}
if (type === 'error') {
alertTypeClass = 'alert-error'
alertTypeIcon = `<i class="fa-regular fa-circle-xmark"></i>`
}
</script>
<div
class="alert alert-{alertType} d-flex gap-3 align-items-center"
role="alert"
>
{#if icon}
<i class={icon} />
{/if}
{#if message}
<div
class="alert mb-4 {alertTypeClass} {additionalClasses} justify-center"
transition:slide
role="alert"
>
{@html alertTypeIcon}
<div class="w-100">
{#if title}<p class="fw-bold mb-0">{title}</p>{/if}
{#if text}
{text}
{:else}
<slot />
{/if}
<span>{message}</span>
</div>
</div>
{/if}

View File

@ -4,10 +4,10 @@
import MediaQuery from '$components/MediaQuery.svelte'
import { DISCORD_URL, DOCS_URL } from '$src/env'
import InstancesGuard from '$src/routes/InstancesGuard.svelte'
import { handleLogoutAndRedirect } from '$util/database'
import { globalInstancesStore } from '$util/stores'
import { values } from '@s-libs/micro-dash'
import UserLoggedIn from './helpers/UserLoggedIn.svelte'
import { client } from '$src/pocketbase-client'
type TypeInstanceObject = {
id: string
@ -25,6 +25,17 @@
}
}
// Log the user out and redirect them to the homepage
const handleLogoutAndRedirect = async () => {
const { logOut } = client()
// Clear out the pocketbase information about the current user
logOut()
// Hard refresh to make sure any remaining data is cleared
window.location.href = '/'
}
const linkClasses =
'font-medium text-xl text-base-content btn btn-ghost capitalize justify-start'
const subLinkClasses =

View File

@ -1,20 +1,23 @@
<script lang="ts">
import { handleResendVerificationEmail } from '$util/database'
import { slide } from 'svelte/transition'
import { isUserLoggedIn, isUserVerified } from '$util/stores'
import { client } from '$src/pocketbase-client'
const { resendVerificationEmail } = client()
let isButtonProcessing: boolean = false
let formError: string = ''
const handleClick = () => {
const handleClick = async () => {
// Update the state
isButtonProcessing = true
handleResendVerificationEmail((error) => {
formError = error
isButtonProcessing = false
})
try {
await resendVerificationEmail()
} catch (error) {
const e = error as Error
formError = `Something went wrong with sending the verification email. ${e.message}`
}
// Wait a bit after the success before showing the button again
setTimeout(() => {

View File

@ -1,6 +1,8 @@
<script lang="ts">
import { slide } from 'svelte/transition'
import { handleLogin } from '$util/database'
import { client } from '$src/pocketbase-client'
import AlertBar from '$components/AlertBar.svelte'
const { authViaEmail } = client()
export let isSignUpView: boolean = true
@ -29,7 +31,7 @@
formError = ''
try {
await handleLogin(email, password)
await authViaEmail(email, password)
} catch (error) {
const e = error as Error
formError = `Something went wrong with logging you in. ${e.message}`
@ -74,12 +76,7 @@
/>
</div>
{#if formError}
<div transition:slide class="alert alert-error mb-5">
<i class="fa-solid fa-circle-exclamation"></i>
<span>{formError}</span>
</div>
{/if}
<AlertBar message={formError} type="error" />
<div class="card-actions justify-end">
<button

View File

@ -2,7 +2,7 @@
import { client } from '$src/pocketbase-client'
import { handleInstanceGeneratorWidget } from '$util/database'
import { writable } from 'svelte/store'
import { slide } from 'svelte/transition'
import AlertBar from '$components/AlertBar.svelte'
export let isProcessing: boolean = false
export let isSignUpView: boolean = false
@ -177,12 +177,7 @@
/>
</div>
{#if formError}
<div transition:slide class="alert alert-error mb-5">
<i class="fa-solid fa-circle-exclamation"></i>
<span>{formError}</span>
</div>
{/if}
<AlertBar message={formError} type="error" />
<div class="card-actions justify-end">
<button

View File

@ -51,44 +51,66 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
const logOut = () => authStore.clear()
const createUser = (email: string, password: string) =>
client
.collection('users')
.create({
email,
password,
passwordConfirm: password,
})
.then(() => {
return client.collection('users').requestVerification(email)
})
/**
* This will register a new user into Pocketbase, and email them a verification link
* @param email {string} The email of the user
* @param password {string} The password of the user
*/
const createUser = async (email: string, password: string) => {
// Build the new user object and any additional properties needed
const data = {
email,
password,
passwordConfirm: password,
}
const confirmVerification = (token: string) =>
client
.collection('users')
.confirmVerification(token)
.then((response) => {
return response
})
// Create the user
const record = await client.collection('users').create(data)
const requestPasswordReset = (email: string) =>
client
.collection('users')
.requestPasswordReset(email)
.then(() => {
return true
})
// Send the verification email
await resendVerificationEmail()
const requestPasswordResetConfirm = (token: string, password: string) =>
client
return record
}
/**
* This will let a user confirm their new account via a token in their email
* @param token {string} The token from the verification email
*/
const confirmVerification = async (token: string) => {
return await client.collection('users').confirmVerification(token)
}
/**
* This will reset an unauthenticated user's password by sending a verification link to their email, and includes an optional error handler
* @param email {string} The email of the user
*/
const requestPasswordReset = async (email: string) => {
return await client.collection('users').requestPasswordReset(email)
}
/**
* This will let an unauthenticated user save a new password after verifying their email
* @param token {string} The token from the verification email
* @param password {string} The new password of the user
*/
const requestPasswordResetConfirm = async (
token: string,
password: string,
) => {
return await client
.collection('users')
.confirmPasswordReset(token, password, password)
.then((response) => {
return response
})
}
const authViaEmail = (email: string, password: string) =>
client.collection('users').authWithPassword(email, password)
/**
* This will log a user into Pocketbase, and includes an optional error handler
* @param {string} email The email of the user
* @param {string} password The password of the user
*/
const authViaEmail = async (email: string, password: string) => {
return await client.collection('users').authWithPassword(email, password)
}
const refreshAuthToken = () => client.collection('users').authRefresh()
@ -163,7 +185,7 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
}
/**
* Use synthetic event for authStore changers so we can broadcast just
* Use synthetic event for authStore changers, so we can broadcast just
* the props we want and not the actual authStore object.
*/
const [onAuthChange, fireAuthChange] = createGenericSyncEvent<BaseAuthStore>()

View File

@ -1,8 +1,6 @@
<script lang="ts">
import { page } from '$app/stores'
import { assertExists } from '$shared'
import { INSTANCE_ADMIN_URL } from '$src/env'
import { slide } from 'svelte/transition'
import Code from './Code.svelte'
import AdminSync from './Danger/AdminSync.svelte'
import DangerZoneTitle from './Danger/DangerZoneTitle.svelte'
@ -14,11 +12,7 @@
import Logging from './Logging.svelte'
import Secrets from './Secrets/Secrets.svelte'
import { instance } from './store'
const { instanceId } = $page.params
console.log(instanceId)
let isReady = false
import AlertBar from '$components/AlertBar.svelte'
$: ({ status, version } = $instance)
@ -59,12 +53,10 @@
</div>
{#if $instance.maintenance}
<div transition:slide class="alert alert-warning mb-6">
<i class="fa-regular fa-triangle-person-digging"></i>
<span
>This instance is in Maintenance Mode and will not respond to requests</span
>
</div>
<AlertBar
message="This instance is in Maintenance Mode and will not respond to requests"
type="warning"
/>
{/if}
<div class="grid lg:grid-cols-2 grid-cols-1 gap-4 mb-4">

View File

@ -4,7 +4,7 @@
import { DOCS_URL } from '$src/env'
import { client } from '$src/pocketbase-client'
import { instance } from '../store'
import ErrorMessage from './ErrorMessage.svelte'
import AlertBar from '$components/AlertBar.svelte'
const { updateInstance } = client()
@ -47,7 +47,7 @@
})
.then(() => 'saved')
.catch((error) => {
error.data.message || error.message
errorMessage = error.message
})
}
@ -68,7 +68,7 @@
else choose it.
</p>
<ErrorMessage message={errorMessage} />
<AlertBar message={errorMessage} type="error" />
<form
class="flex rename-instance-form-container-query gap-4"

View File

@ -4,8 +4,8 @@
import { DOCS_URL } from '$src/env'
import { client } from '$src/pocketbase-client'
import { instance } from '../../store'
import ErrorMessage from '../ErrorMessage.svelte'
import VersionPicker from './VersionPicker.svelte'
import AlertBar from '$components/AlertBar.svelte'
$: ({ id, maintenance, version } = $instance)
@ -46,7 +46,7 @@
return 'saved'
})
.catch((error) => {
error.data.message || error.message
errorMessage = error.message
})
} else {
// If they hit cancel, reset the version number back to what it was initially
@ -72,7 +72,7 @@
> of PocketBase.
</p>
<ErrorMessage message={errorMessage} />
<AlertBar message={errorMessage} type="error" />
<form
class="flex change-version-form-container-query gap-4"

View File

@ -2,9 +2,9 @@
import { SECRET_KEY_REGEX, UpdateInstancePayload } from '$shared'
import { client } from '$src/pocketbase-client/index.js'
import { reduce } from '@s-libs/micro-dash'
import { slide } from 'svelte/transition'
import { instance } from '../store.js'
import { items } from './stores.js'
import AlertBar from '$components/AlertBar.svelte'
// Keep track of the new key and value to be added
let secretKey: string = ''
@ -113,11 +113,10 @@
</div>
{#if !isKeyValid && secretKey.length > 0}
<div in:slide class="alert alert-error mb-4">
<i class="fa-regular fa-circle-exclamation"></i>
All key names must be upper case, alphanumeric, and may include underscore
(_).
</div>
<AlertBar
message="All key names must be upper case, alphanumeric, and may include underscore (_)."
type="error"
/>
{/if}
<div class="text-right">
@ -128,16 +127,11 @@
</form>
{#if successfulSave}
<div in:slide class="alert alert-success">
<i class="fa-regular fa-shield-check"></i>
Your new environment variable has been saved.
</div>
<AlertBar
message="Your new environment variable has been saved."
type="success"
/>
{/if}
{#if errorMessage}
<div in:slide class="alert alert-error mb-4">
<i class="fa-regular fa-circle-exclamation"></i>
{errorMessage}
</div>
{/if}
<AlertBar message={errorMessage} type="error" />
</div>

View File

@ -1,7 +1,6 @@
<script lang="ts">
import Card from '$components/cards/Card.svelte'
import CardHeader from '$components/cards/CardHeader.svelte'
import { INSTANCE_URL } from '$src/env'
import { handleCreateNewInstance } from '$util/database'
import { slide } from 'svelte/transition'
import { writable } from 'svelte/store'

View File

@ -21,8 +21,6 @@
>
</div>
<!--<UsageChartForAllInstances />-->
<InstanceList />
{/if}
</AuthStateGuard>

View File

@ -4,9 +4,6 @@
import { globalInstancesStore } from '$util/stores'
import InstanceRow from '$src/routes/dashboard/InstanceRow.svelte'
import { values } from '@s-libs/micro-dash'
import { slide } from 'svelte/transition'
let isMaintenanceModeOpen = false
type TypeInstanceObject = {
id: string
@ -39,7 +36,7 @@
<InstanceRow {instance} {index} />
{/each}
{#if arrayOfMaintenanceInstances.length === 0}
{#if arrayOfActiveInstances.length === 0}
<p class="italic">
None of your instances are active. Create a new app to use PocketBase!
</p>

View File

@ -103,7 +103,7 @@
)
// Loop through the instance list again, and create a "total usage" entry
const totalUsageAmount = allInstancesArray.reduce((total) => total + 0, 0)
const totalUsageAmount = allInstancesArray.reduce((total) => total, 0)
// Add up the individual instance usages and the total usage
const allChartData: any = [

View File

@ -1,6 +1,8 @@
<script lang="ts">
import { slide } from 'svelte/transition'
import { handleLogin } from '$util/database'
import { client } from '$src/pocketbase-client'
const { authViaEmail } = client()
let email: string = ''
let password: string = ''
@ -15,7 +17,7 @@
isFormButtonDisabled = true
try {
await handleLogin(email, password)
await authViaEmail(email, password)
} catch (error) {
const e = error as Error
formError = `Something has gone wrong with logging in. ${e.message}`

View File

@ -1,7 +1,9 @@
<script lang="ts">
import { page } from '$app/stores'
import { slide } from 'svelte/transition'
import { handleAccountConfirmation } from '$util/database'
import { client } from '$src/pocketbase-client'
const { confirmVerification } = client()
let token: string | null = ''
let formError: string = ''
@ -20,7 +22,7 @@
const handleLoad = async () => {
try {
await handleAccountConfirmation(token)
await confirmVerification(token)
// Refresh the app to get the latest info from the backend
window.location.href = '/'

View File

@ -1,6 +1,8 @@
<script lang="ts">
import { handleUnauthenticatedPasswordReset } from '$util/database'
import { slide } from 'svelte/transition'
import { client } from '$src/pocketbase-client'
import AlertBar from '$components/AlertBar.svelte'
const { requestPasswordReset } = client()
let email: string = ''
let formError: string = ''
@ -15,9 +17,12 @@
isFormButtonDisabled = true
await handleUnauthenticatedPasswordReset(email, (error) => {
formError = error
})
try {
await requestPasswordReset(email)
} catch (error) {
const e = error as Error
formError = `Something went wrong with resetting you password. ${e.message}`
}
isFormButtonDisabled = false
userShouldCheckTheirEmail = true
@ -59,12 +64,7 @@
/>
</div>
{#if formError}
<div transition:slide class="alert alert-error mb-5">
<i class="fa-solid fa-circle-exclamation"></i>
<span>{formError}</span>
</div>
{/if}
<AlertBar message={formError} type="error" />
<div class="mt-4 card-actions justify-end">
<button

View File

@ -1,7 +1,9 @@
<script lang="ts">
import { page } from '$app/stores'
import { handleUnauthenticatedPasswordResetConfirm } from '$util/database'
import { slide } from 'svelte/transition'
import { client } from '$src/pocketbase-client'
import AlertBar from '$components/AlertBar.svelte'
const { requestPasswordResetConfirm } = client()
let password: string = ''
let token: string | null = ''
@ -16,19 +18,28 @@
const handleSubmit = async (e: Event) => {
e.preventDefault()
// Clear out the error message
formError = ''
// Check for the token and block the request if it doesn't exist
if (!token) {
formError =
'No token was found. Please check your email again for the link.'
return
}
// Lock the button to prevent multiple submissions
isFormButtonDisabled = true
if (!token) return
await handleUnauthenticatedPasswordResetConfirm(
token,
password,
(error) => {
formError = error
},
)
try {
await requestPasswordResetConfirm(token, password)
// Hard refresh and send the user back to the login screen
window.location.href = '/?view=login'
// Hard refresh and send the user back to the login screen
window.location.href = '/?view=login'
} catch (error) {
const e = error as Error
formError = `Something went wrong with confirming your password change. ${e.message}`
}
isFormButtonDisabled = false
}
@ -56,12 +67,7 @@
/>
</div>
{#if formError}
<div transition:slide class="alert alert-error mb-5">
<i class="fa-solid fa-circle-exclamation"></i>
<span>{formError}</span>
</div>
{/if}
<AlertBar message={formError} type="error" />
<div class="mt-4 card-actions justify-end">
<button

View File

@ -1,6 +1,8 @@
<script lang="ts">
import { slide } from 'svelte/transition'
import { handleLogin, handleRegistration } from '$util/database'
import { client } from '$src/pocketbase-client'
const { createUser, authViaEmail } = client()
let email: string = ''
let password: string = ''
@ -16,10 +18,11 @@
formError = ''
try {
await handleRegistration(email, password)
// Create the new user and email the verification link
await createUser(email, password)
// Go ahead and log the user into the site
await handleLogin(email, password)
await authViaEmail(email, password)
} catch (error) {
const e = error as Error
formError = `Something went wrong with registering your account. ${e.message}`

View File

@ -14,86 +14,6 @@ export const handleFormError = (e: Error, setError?: FormErrorHandler) => {
}
}
/**
* This will log a user into Pocketbase, and includes an optional error handler
* @param {string} email The email of the user
* @param {string} password The password of the user
*/
export const handleLogin = async (email: string, password: string) => {
const { authViaEmail } = client()
return await authViaEmail(email, password)
}
/**
* This will register a new user into Pocketbase, and includes an optional error handler
* @param email {string} The email of the user
* @param password {string} The password of the user
*/
export const handleRegistration = async (email: string, password: string) => {
const { createUser } = client()
return await createUser(email, password)
}
/**
* This will let a user confirm their new account email, and includes an optional error handler
* @param token {string} The token from the verification email
*/
export const handleAccountConfirmation = async (token: string) => {
const { confirmVerification } = client()
return await confirmVerification(token)
}
/**
* This will reset an unauthenticated user's password by sending a verification link to their email, and includes an optional error handler
* @param email {string} The email of the user
* @param setError {function} This can be used to show an alert bar if an error occurs during the login process
*/
export const handleUnauthenticatedPasswordReset = async (
email: string,
setError?: FormErrorHandler,
) => {
const { requestPasswordReset } = client()
// Reset the form error if the form is submitted
setError?.('')
try {
return await requestPasswordReset(email)
} catch (error: any) {
handleFormError(error, setError)
}
return false
}
/**
* This will let an unauthenticated user save a new password after verifying their email, and includes an optional error handler
* @param token {string} The token from the verification email
* @param password {string} The new password of the user
* @param setError {function} This can be used to show an alert bar if an error occurs during the login process
*/
export const handleUnauthenticatedPasswordResetConfirm = async (
token: string,
password: string,
setError?: FormErrorHandler,
) => {
const { requestPasswordResetConfirm } = client()
// Reset the form error if the form is submitted
setError?.('')
try {
await requestPasswordResetConfirm(token, password)
await goto('/')
} catch (error: any) {
handleFormError(error, setError)
}
return false
}
export const handleCreateNewInstance = async (
instanceName: string,
setError?: FormErrorHandler,
@ -103,7 +23,7 @@ export const handleCreateNewInstance = async (
const { id } = user() || {}
try {
// Prechecks
// Pre-checks
if (!instanceName) throw new Error(`Instance name is required`)
if (!id) throw new Error(`Must be logged in to create an instance`)
@ -124,14 +44,20 @@ export const handleInstanceGeneratorWidget = async (
instanceName: string,
setError = (value: string) => {},
) => {
const { authViaEmail } = client()
try {
await client().client.send(`/api/signup`, {
method: 'POST',
body: { email, password, instanceName },
})
await handleLogin(email, password)
await authViaEmail(email, password)
const instance = await client().getInstanceBySubdomain(instanceName)
if (!instance) throw new Error(`This should never happen`)
window.location.href = `/app/instances/${instance.id}`
} catch (e) {
if (e instanceof Error) {
@ -139,27 +65,3 @@ export const handleInstanceGeneratorWidget = async (
}
}
}
export const handleResendVerificationEmail = async (
setError = (value: string) => {},
) => {
const { resendVerificationEmail } = client()
try {
await resendVerificationEmail()
} catch (error: any) {
handleFormError(error, setError)
}
}
export const handleLogout = () => {
const { logOut } = client()
// Clear the Pocketbase session
logOut()
}
export const handleLogoutAndRedirect = () => {
handleLogout()
// Hard refresh to make sure any remaining data is cleared
window.location.href = '/'
}