Compare commits
187 Commits
Author | SHA1 | Date | |
---|---|---|---|
2e19d5fdeb | |||
63f31e0f4c | |||
f75d889ee8 | |||
8877362484 | |||
082c161959 | |||
5d96002a2e | |||
65ddd704f0 | |||
b12d095e3e | |||
581ceb028a | |||
7ac84fc0cf | |||
164aec1c51 | |||
420e029d66 | |||
d94f3b825b | |||
7635178e3c | |||
a9968ccb03 | |||
4d86b8aeed | |||
19bee6998a | |||
fcf83c6435 | |||
32ba288463 | |||
334db37d18 | |||
1112191b61 | |||
52b7771994 | |||
9e76f9315b | |||
c9bf8e8431 | |||
691292becc | |||
e8fa3d2ce0 | |||
fd76220269 | |||
e9e4f25520 | |||
234cc989f1 | |||
e6a81a3bdf | |||
96aeca6242 | |||
a6d050c35f | |||
f790a03c27 | |||
6f5bf90a0b | |||
e5daff376a | |||
a0a38389a6 | |||
85c066baec | |||
9c6bdddd22 | |||
87ba2e032d | |||
28414630a4 | |||
534cbf2957 | |||
481b4f698c | |||
172a3015af | |||
6564be1a60 | |||
fdcaccfbd6 | |||
6541bd94fe | |||
82fdd61170 | |||
caca715cc0 | |||
74335ff6cf | |||
3206a19f7a | |||
e48d3ebafb | |||
168c835a23 | |||
0127d00021 | |||
02872252b5 | |||
b6736651c5 | |||
78a25ab398 | |||
58d384a3fc | |||
76a09c8e8c | |||
73836c5893 | |||
4c972778cd | |||
795fc0f5f7 | |||
e4dde90da4 | |||
fa6bf10249 | |||
cd5d0bf304 | |||
4a3c7023ba | |||
ea3a14a6e4 | |||
451bdd1106 | |||
2b21c2feff | |||
02e85c54fc | |||
ac4007a82e | |||
e584a43145 | |||
b232be9377 | |||
46a20bbd7e | |||
894fb58c29 | |||
2c59660566 | |||
666e377cdb | |||
da6fc56b86 | |||
5daa585a9e | |||
d33f36adaf | |||
e03816928a | |||
de08c2ddd0 | |||
9de286f920 | |||
45d3b459d8 | |||
712e1ce5cd | |||
912fa157e2 | |||
394163198c | |||
f74d7a8fa9 | |||
cfe47be428 | |||
bd334bcbea | |||
54ec36cebc | |||
f175b31976 | |||
9b5c3d44c5 | |||
97f2a31951 | |||
4c564df94a | |||
2edf752b05 | |||
d7f3330dc2 | |||
afe4076874 | |||
f86c618b1d | |||
7fec3450d9 | |||
41c90c2a58 | |||
bef53cadce | |||
f30d2c5a2a | |||
f2bd76487e | |||
92c2317af1 | |||
0b390b7658 | |||
8a994123af | |||
dcfe7c5676 | |||
21fa376190 | |||
23137d4e04 | |||
b99f3fa256 | |||
2c036794ff | |||
ee1272b520 | |||
2cccc65a9a | |||
e17aba510c | |||
381009d5c9 | |||
401e3d41dc | |||
82728cbc97 | |||
de0a5a8e73 | |||
057c20cd21 | |||
b2f700280a | |||
e8161fc226 | |||
10f8e5d289 | |||
54a771b47f | |||
e82305954e | |||
fe09172881 | |||
caac354273 | |||
ea21548c68 | |||
eb8df7bed7 | |||
4b0060e5dd | |||
08ce73e64e | |||
af0dd625b4 | |||
43432a8bcd | |||
5e5678c280 | |||
c0a382e4a7 | |||
ab59264d84 | |||
ea68c28b2e | |||
6cd71b64e8 | |||
b93f3945bf | |||
53fcbb76f6 | |||
8ad20079de | |||
10f36e54ff | |||
e930b2ce73 | |||
b5a796d3ff | |||
abab8c9336 | |||
82399d1bc3 | |||
cb37b333b0 | |||
adfab3585b | |||
052bc21604 | |||
1647474a74 | |||
cf7e485f4e | |||
ab0198df01 | |||
a48488a128 | |||
82efd1f10e | |||
70ea1d8a9c | |||
97eba7457d | |||
8eedebfee2 | |||
9f205dd034 | |||
f7cef69b5b | |||
090630c507 | |||
6af5a0ee5d | |||
323ba05fda | |||
8f41b42cf4 | |||
58a10bcc41 | |||
b1bd8ab67e | |||
f53ea97c3e | |||
21307711e6 | |||
7780a3a0d1 | |||
7bf385f8c0 | |||
867af6ac1d | |||
0203f848b1 | |||
02f95efe7b | |||
4fcb80fd75 | |||
eafa726b43 | |||
50fcd06c17 | |||
ef5f02176e | |||
b12d3e1752 | |||
812c01e518 | |||
4c16ebd1d7 | |||
246eb848f8 | |||
8cf5e0b827 | |||
1286003442 | |||
3291d70361 | |||
b14c9d54ad | |||
d8e30415cb | |||
a3382a6434 | |||
396bb68b14 | |||
138f34169e |
@ -1,3 +1,2 @@
|
||||
NEXT_APPWRITE_KEY=<API_KEY>
|
||||
NEXT_PUBLIC_APPWRITE_ENDPOINT=http://localhost/v1
|
||||
NEXT_PUBLIC_APPWRITE_PROJECT=<PROJECT_ID>
|
||||
NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
|
5
.env
@ -1,3 +1,2 @@
|
||||
NEXT_PUBLIC_SUPABASE_URL=http://localhost:8000
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
|
||||
NEXT_PUBLIC_
|
||||
NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
|
2
.env.production
Normal file
@ -0,0 +1,2 @@
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://cgnmpfjcbhtgdcizgfnc.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNnbm1wZmpjYmh0Z2RjaXpnZm5jIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTY4NTkyNDIsImV4cCI6MjAzMjQzNTI0Mn0.oGf2jp-Twr1qP7GQI4D87iu5EWV6UhL51OEaCLvSMxk
|
6
.gitignore
vendored
@ -21,3 +21,9 @@ yarn-error.log*
|
||||
/owncast
|
||||
/coverage
|
||||
/supabase/volumes/db/data
|
||||
/supabase/volumes/storage
|
||||
supabase.zip
|
||||
.idea/copilot/chatSessions/00000000000.xd
|
||||
.idea/copilot/chatSessions/blobs/version
|
||||
.idea/copilot/chatSessions/xd.lck
|
||||
.idea/workspace.xml
|
||||
|
5
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# GitHub Copilot persisted chat sessions
|
||||
/copilot/chatSessions
|
1
.idea/.name
generated
Normal file
@ -0,0 +1 @@
|
||||
page.tsx
|
17
.idea/aws.xml
generated
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="accountSettings">
|
||||
<option name="activeProfile" value="profile:default" />
|
||||
<option name="activeRegion" value="us-east-1" />
|
||||
<option name="recentlyUsedProfiles">
|
||||
<list>
|
||||
<option value="profile:default" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="recentlyUsedRegions">
|
||||
<list>
|
||||
<option value="us-east-1" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
4
.idea/encodings.xml
generated
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
6
.idea/misc.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_22" default="true" project-jdk-name="openjdk-22" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/neroshitron.iml" filepath="$PROJECT_DIR$/.idea/neroshitron.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
11
.idea/neroshitron.iml
generated
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.idea/copilot/chatSessions" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["denoland.vscode-deno"]
|
||||
}
|
10
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"deno.enablePaths": [
|
||||
"supabase/functions"
|
||||
],
|
||||
"deno.lint": true,
|
||||
"deno.unstable": true,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
}
|
||||
}
|
89
README.md
@ -1,75 +1,54 @@
|
||||
# Neroshitron
|
||||

|
||||

|
||||
|
||||
|
||||
# Documentation For Technical Stack
|
||||
- https://nextjs.org/docs
|
||||
- https://supabase.com/docs/guides/self-hosting/docker
|
||||
- https://www.docker.com/products/docker-desktop/
|
||||
|
||||
- https://supabase.com/docs/
|
||||
- https://owncast.online/docs/
|
||||
- https://docs.docker.com/engine/install/
|
||||
|
||||
# Running Backend
|
||||
You will need docker installed.
|
||||
- https://docs.docker.com/engine/install/
|
||||
|
||||
You will need supabase CLI.
|
||||
- https://docs.docker.com/engine/install/
|
||||
|
||||
You need npm and nodejs installed. See documentation at start of document.
|
||||
- https://docs.npmjs.com/downloading-and-installing-node-js-and-npm
|
||||
|
||||
1) Open your terminal and navigate to the root of the git repository.
|
||||
2) Make sure that docker and docker compose are installed.
|
||||
3) Run `docker-compose --env-file ./docker.env up` which will start up OwnCast, AppWrite, and the UI.
|
||||
3) Run `docker-compose --env-file ./docker.env up` which will start up OwnCast.
|
||||
4) Run `supabase start`
|
||||
5) Open your terminal and navigate to the root folder of the git repository.
|
||||
6) Run the command `npm update`.
|
||||
7) Once the depedencies are pulled and installed you can run the command `npm run dev` to run the application in development mode.
|
||||
8) Open http://localhost:3000/
|
||||
|
||||
## MailDev
|
||||
http://localhost:1080
|
||||
|
||||
|
||||
### Updating the database/Seeding data
|
||||
Run `supabase db reset`. This will wipe data.
|
||||
https://supabase.com/docs/guides/cli/local-development?queryGroups=access-method&access-method=kong#database-migrations
|
||||
|
||||
** Once the data is seeded you will need to go to the galleries bucket and add images to the folders that exist in it for the seeded galleries. **
|
||||
|
||||
|
||||
## inbucket
|
||||
http://localhost:54324su/monitor
|
||||
This is where all mail being sent shows up from the application for developers.
|
||||
|
||||
## OwnCast
|
||||
http://localhost:8080/
|
||||
Configuration is done through the Owncast administration page located on your server under /admin. The login username is admin and the password is your stream key, the default being abc123.
|
||||
|
||||
## Supabase
|
||||
http://localhost:8000/
|
||||
You will need to register and sign up, the first account on the appwrite instance will be the admin account.çConfiguration is done through the Owncast administration page located on your server under /admin. The login username is admin and the password is your stream key, the default being abc123.
|
||||
# User Flow Diagram
|
||||

|
||||
|
||||
# Running UI
|
||||
1) Open your terminal and navigate to the root folder of the git repository.
|
||||
2) Run the command `npm update`.
|
||||
3) Once the depedencies are pulled and installed you can run the command `npm run dev` to run the application in development mode.
|
||||
4) Open http://localhost:3000/
|
||||
# Database Diagram
|
||||

|
||||
|
||||
# React Components
|
||||
|
||||
## Gallery Component
|
||||
The `Gallery` component is a React component used to display a gallery of images. It fetches images from an API and displays them in a Masonry layout.
|
||||
### Props
|
||||
- `id` (number): The ID of the gallery to fetch images from.
|
||||
- `closeMenu` (function): A function to close the menu.
|
||||
### State
|
||||
- `isSingle` (boolean): A state to check if only a single image is to be displayed.
|
||||
- `loaded` (object): A state to keep track of loaded images.
|
||||
- `selectedImage` (string | null): A state to keep track of the selected image.
|
||||
- `images` (string[]): A state to store the fetched images.
|
||||
- `galleryId` (number): A state to store the gallery ID.
|
||||
### Functions
|
||||
- `getData`: An async function to fetch images from the API.
|
||||
- `generateRandomString`: A function to generate a random string of a given length.
|
||||
- `handleDownload`: A function to handle the download of an image.
|
||||
### Usage
|
||||
The example below will load the images of the the gallery with an ID of `58201557-b392-471e-ac55-dcf6171cd18d` and will call the function for `setIsOpen(false)` when the back button is clicked.
|
||||
```tsx
|
||||
<Gallery id={"58201557-b392-471e-ac55-dcf6171cd18d"} closeMenu={() => setIsOpen(false)}></Gallery>
|
||||
```
|
||||
## GalleryThumbnail Component
|
||||
The `GalleryThumbnail` component is a React component used to display a thumbnail of a gallery. It fetches the thumbnail image from an API and displays it. When clicked, it triggers a callback function with the gallery ID.
|
||||
### Props
|
||||
|
||||
- `id` (string): The ID of the gallery to fetch the thumbnail for.
|
||||
- `onSelect` (function): A function to be called when the thumbnail is clicked. The gallery ID is passed as an argument.
|
||||
### State
|
||||
|
||||
- `galleryId` (string): A state to store the gallery ID.
|
||||
- `thumbnailUrl` (string): A state to store the fetched thumbnail URL.
|
||||
- `isLoading` (boolean): A state to keep track of the loading status.
|
||||
### Functions
|
||||
|
||||
- `openGallery`: A function to call the `onSelect` prop with the gallery ID.
|
||||
- `getData`: An async function to fetch the thumbnail from the API.
|
||||
### Usage
|
||||
This will render a thumbnail for the gallery with the ID of "1". When the thumbnail is clicked, it will log the gallery ID to the console.
|
||||
```tsx
|
||||
<GalleryThumbnail id="1" onSelect={(id) => console.log(id)} />
|
||||
```
|
||||
|
4
app/_middleware.tsx
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
import { middleware } from './middleware/path-header'
|
||||
|
||||
export default middleware
|
41
app/admin/page.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
"use client";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
function PageComponent() {
|
||||
const supabase = createClient();
|
||||
const getData = async () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
const router = useRouter();
|
||||
return (
|
||||
<div className="w-full text-white flex justify-center items-center animate-in">
|
||||
<div className="w-2/3 lg:w-1/3 rounded-md bg-primary p-8 mt-32 shadow-lg opacity-90 backdrop-blur-lg">
|
||||
<div className="w-full flex justify-center">
|
||||
<span className="text-2xl pb-4">System Settings</span>
|
||||
</div>
|
||||
<div className="flex justify-center ">
|
||||
<button onClick={()=>{ router.push("/admin/tiers") }} className="bg-secondary hover:bg-secondary-dark rounded p-2 mr-1 w-full">Tiers Management</button>
|
||||
<button onClick={()=>{ router.push("/gallery/admin") }} className="ml-1 bg-secondary hover:bg-secondary-dark rounded p-2 w-full">Gallery Management</button>
|
||||
|
||||
</div>
|
||||
<div className="flex justify-center pt-2">
|
||||
<button onClick={()=>{ router.push("/admin/theme") }} className="mr-1 bg-secondary hover:bg-secondary-dark rounded p-2 mr-1 w-full">Theme Settings</button>
|
||||
<button className="bg-secondary hover:bg-secondary-dark rounded p-2 ml-1 w-full">User Management</button>
|
||||
</div>
|
||||
<div className="flex justify-center pt-2">
|
||||
<button className="bg-secondary hover:bg-secondary-dark rounded p-2 w-full mr-1">Payment/Payout Settings</button>
|
||||
<button className="bg-secondary hover:bg-secondary-dark rounded p-2 w-full ml-1">Comissions Settings</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PageComponent;
|
||||
|
||||
|
||||
|
474
app/admin/theme/page.tsx
Normal file
@ -0,0 +1,474 @@
|
||||
"use client";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
function PageComponent() {
|
||||
const router = useRouter();
|
||||
const supabase = createClient();
|
||||
|
||||
const [primary, setPrimary] = useState('#201240');
|
||||
const [primaryLight, setPrimaryLight] = useState('#403260');
|
||||
const [primaryDark, setPrimaryDark] = useState('#100120');
|
||||
const [secondary, setSecondary] = useState('#4F3D70');
|
||||
const [secondaryLight, setSecondaryLight] = useState('#6F5D90');
|
||||
const [secondaryDark, setSecondaryDark] = useState('#2F1D50');
|
||||
const [error, setError] = useState('#862117');
|
||||
const [errorLight, setErrorLight] = useState('#C44C4C');
|
||||
const [errorDark, setErrorDark] = useState('#5C0D0D');
|
||||
const [changed, setChanged] = useState(false);
|
||||
const [success, setSuccess] = useState('#00C9A6');
|
||||
const [successLight, setSuccessLight] = useState('#20E9C6');
|
||||
const [successDark, setSuccessDark] = useState('#00A986');
|
||||
const [warning, setWarning] = useState('#E17558');
|
||||
const [warningLight, setWarningLight] = useState('#E39578');
|
||||
const [warningDark, setWarningDark] = useState('#C15538');
|
||||
const [info, setInfo] = useState('#222140');
|
||||
const [infoLight, setInfoLight] = useState('#424260');
|
||||
const [infoDark, setInfoDark] = useState('#020120');
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const getData = async () => {
|
||||
var primaryConfig = await getColorConfig('primary');
|
||||
setPrimary(primaryConfig.value);
|
||||
var primaryLightConfig = await getColorConfig('primary-light');
|
||||
setPrimaryLight(primaryLightConfig.value);
|
||||
var primaryDarkConfig = await getColorConfig('primary-dark');
|
||||
setPrimaryDark(primaryDarkConfig.value);
|
||||
var secondaryConfig = await getColorConfig('secondary');
|
||||
setSecondary(secondaryConfig.value);
|
||||
var secondaryLightConfig = await getColorConfig('secondary-light');
|
||||
setSecondaryLight(secondaryLightConfig.value);
|
||||
var secondaryDarkConfig = await getColorConfig('secondary-dark');
|
||||
setSecondaryDark(secondaryDarkConfig.value);
|
||||
var errorConfig = await getColorConfig('error');
|
||||
setError(errorConfig.value);
|
||||
var errorLightConfig = await getColorConfig('error-light');
|
||||
setErrorLight(errorLightConfig.value);
|
||||
var errorDarkConfig = await getColorConfig('error-dark');
|
||||
setErrorDark(errorDarkConfig.value);
|
||||
var successConfig = await getColorConfig('success');
|
||||
setSuccess(successConfig.value);
|
||||
var successLightConfig = await getColorConfig('success-light');
|
||||
setSuccessLight(successLightConfig.value);
|
||||
var successDarkConfig = await getColorConfig('success-dark');
|
||||
setSuccessDark(successDarkConfig.value);
|
||||
var warningConfig = await getColorConfig('warning');
|
||||
setWarning(warningConfig.value);
|
||||
var warningLightConfig = await getColorConfig('warning-light');
|
||||
setWarningLight(warningLightConfig.value);
|
||||
var warningDarkConfig = await getColorConfig('warning-dark');
|
||||
setWarningDark(warningDarkConfig.value);
|
||||
var infoConfig = await getColorConfig('info');
|
||||
setInfo(infoConfig.value);
|
||||
var infoLightConfig = await getColorConfig('info-light');
|
||||
setInfoLight(infoLightConfig.value);
|
||||
var infoDarkConfig = await getColorConfig('info-dark');
|
||||
setInfoDark(infoDarkConfig.value);
|
||||
}
|
||||
|
||||
const getColorConfig = async (name: string) => {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/interface-configs?name=${name}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to call GET request');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new Error('Failed to call GET request');
|
||||
}
|
||||
}
|
||||
|
||||
const setColorConfig = async (name: string, value:string) => {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/interface-configs`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ name, value }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to call GET request');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new Error('Failed to call GET request');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const save = async () => {
|
||||
setSaving(true);
|
||||
await Promise.all([
|
||||
setColorConfig('primary', primary),
|
||||
setColorConfig('primary-light', primaryLight),
|
||||
setColorConfig('primary-dark', primaryDark),
|
||||
setColorConfig('secondary', secondary),
|
||||
setColorConfig('secondary-light', secondaryLight),
|
||||
setColorConfig('secondary-dark', secondaryDark),
|
||||
setColorConfig('error', error),
|
||||
setColorConfig('error-light', errorLight),
|
||||
setColorConfig('error-dark', errorDark),
|
||||
setColorConfig('success', success),
|
||||
setColorConfig('success-light', successLight),
|
||||
setColorConfig('success-dark', successDark),
|
||||
setColorConfig('warning', warning),
|
||||
setColorConfig('warning-light', warningLight),
|
||||
setColorConfig('warning-dark', warningDark),
|
||||
setColorConfig('info', info),
|
||||
setColorConfig('info-light', infoLight),
|
||||
setColorConfig('info-dark', infoDark)
|
||||
]);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
const colorChange = async ()=>{
|
||||
setChanged(true)
|
||||
document.documentElement.style.setProperty(`--color-primary`, primary);
|
||||
document.documentElement.style.setProperty(`--color-primary-light`, primaryLight);
|
||||
document.documentElement.style.setProperty(`--color-primary-dark`, primaryDark);
|
||||
document.documentElement.style.setProperty(`--color-secondary`, secondary);
|
||||
document.documentElement.style.setProperty(`--color-secondary-light`, secondaryLight);
|
||||
document.documentElement.style.setProperty(`--color-secondary-dark`, secondaryDark);
|
||||
document.documentElement.style.setProperty(`--color-error`, error);
|
||||
document.documentElement.style.setProperty(`--color-error-light`, errorLight);
|
||||
document.documentElement.style.setProperty(`--color-error-dark`, errorDark);
|
||||
document.documentElement.style.setProperty(`--color-success`, success);
|
||||
document.documentElement.style.setProperty(`--color-success-light`, successLight);
|
||||
document.documentElement.style.setProperty(`--color-success-dark`, successDark);
|
||||
document.documentElement.style.setProperty(`--color-warning`, warning);
|
||||
document.documentElement.style.setProperty(`--color-warning-light`, warningLight);
|
||||
document.documentElement.style.setProperty(`--color-warning-dark`, warningDark);
|
||||
document.documentElement.style.setProperty(`--color-info`, info);
|
||||
document.documentElement.style.setProperty(`--color-info-light`, infoLight);
|
||||
document.documentElement.style.setProperty(`--color-info-dark`, infoDark);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-5/6 h-1/2 text-white lg:flex justify-center items-center animate-in overflow-y-hidden">
|
||||
<div className=" w-5/6 md:w-4/6 lg:w-3/6 xl:w-2/6 rounded-md bg-primary opacity-90 p-12 m-1 mt-24 shadow-lg backdrop-blur">
|
||||
<div className="w-full relative">
|
||||
<span className="text-2xl pb-4">Color Settings</span>
|
||||
<button onClick={()=>{router.push("/admin")}} className={`float-right bg-error hover:bg-error-light ml-2 rounded p-2`}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor"
|
||||
className="md:hidden size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 15 3 9m0 0 6-6M3 9h12a6 6 0 0 1 0 12h-3" />
|
||||
</svg>
|
||||
<span className="hidden md:block">Back</span>
|
||||
</button>
|
||||
<button onClick={save} disabled={!changed}
|
||||
className={`float-right ${changed ? "bg-success hover:bg-success-light" : "bg-success-dark"} rounded p-2`}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor"
|
||||
className="md:hidden size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="m4.5 12.75 6 6 9-13.5" />
|
||||
</svg>
|
||||
<span className="hidden md:block">{saving?"Saving...":"Save"}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="w-full relative">
|
||||
<label htmlFor="primaryInput">Primary Color</label>
|
||||
<div className="flex">
|
||||
<input
|
||||
value={primary}
|
||||
required
|
||||
type="text"
|
||||
className="w-full rounded-md rounded-r-none bg-info-bright p-2 text-black shadow-lg"
|
||||
onChange={(e) => { setPrimary(e.target.value); colorChange(); }}
|
||||
placeholder="Choose the primary color"
|
||||
/>
|
||||
<input
|
||||
value={primary}
|
||||
onChange={(e) => { setPrimary(e.target.value); colorChange(); }}
|
||||
required
|
||||
|
||||
type="color"
|
||||
className="w-10 h-10 rounded-r p-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full relative">
|
||||
<label htmlFor="primaryInput">Primary Light Color</label>
|
||||
<div className="flex">
|
||||
<input
|
||||
value={primaryLight}
|
||||
required
|
||||
type="text"
|
||||
className="w-full rounded-md rounded-r-none bg-info-bright p-2 text-black shadow-lg"
|
||||
onChange={(e) => { setPrimaryLight(e.target.value); colorChange(); }}
|
||||
placeholder="Choose the primary color"
|
||||
/>
|
||||
<input
|
||||
value={primaryLight}
|
||||
onChange={(e) => { setPrimaryLight(e.target.value); colorChange(); }}
|
||||
required
|
||||
|
||||
type="color"
|
||||
className="w-10 h-10 rounded-r p-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full relative">
|
||||
<label htmlFor="primaryInput">Primary Dark Color</label>
|
||||
<div className="flex">
|
||||
<input
|
||||
value={primaryDark}
|
||||
required
|
||||
type="text"
|
||||
className="w-full rounded-md rounded-r-none bg-info-bright p-2 text-black shadow-lg"
|
||||
onChange={(e) => { setPrimaryDark(e.target.value); colorChange(); }}
|
||||
placeholder="Choose the primary color"
|
||||
/>
|
||||
<input
|
||||
value={primaryDark}
|
||||
onChange={(e) => { setPrimaryDark(e.target.value); colorChange(); }}
|
||||
required
|
||||
|
||||
type="color"
|
||||
className="w-10 h-10 rounded-r p-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div className="w-full relative">
|
||||
<label htmlFor="primaryInput">Secondary Color</label>
|
||||
<div className="flex">
|
||||
<input
|
||||
value={secondary}
|
||||
required
|
||||
type="text"
|
||||
className="w-full rounded-md rounded-r-none bg-info-bright p-2 text-black shadow-lg"
|
||||
onChange={(e) => { setSecondary(e.target.value); colorChange(); }}
|
||||
placeholder="Choose the primary color"
|
||||
/>
|
||||
<input
|
||||
value={secondary}
|
||||
onChange={(e) => { setSecondary(e.target.value); colorChange(); }}
|
||||
required
|
||||
|
||||
type="color"
|
||||
className="w-10 h-10 rounded-r p-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full relative">
|
||||
<label htmlFor="primaryInput">Secondary Dark Color</label>
|
||||
<div className="flex">
|
||||
<input
|
||||
value={secondaryDark}
|
||||
required
|
||||
type="text"
|
||||
className="w-full rounded-md rounded-r-none bg-info-bright p-2 text-black shadow-lg"
|
||||
onChange={(e) => { setSecondaryDark(e.target.value); colorChange(); }}
|
||||
placeholder="Choose the primary color"
|
||||
/>
|
||||
<input
|
||||
value={secondaryDark}
|
||||
onChange={(e) => { setSecondaryDark(e.target.value); colorChange(); }}
|
||||
required
|
||||
|
||||
type="color"
|
||||
className="w-10 h-10 rounded-r p-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full relative">
|
||||
<label htmlFor="primaryInput">Secondary Light Color</label>
|
||||
<div className="flex">
|
||||
<input
|
||||
value={secondaryLight}
|
||||
required
|
||||
type="text"
|
||||
className="w-full rounded-md rounded-r-none bg-info-bright p-2 text-black shadow-lg"
|
||||
onChange={(e) => { setSecondaryLight(e.target.value); colorChange(); }}
|
||||
placeholder="Choose the primary color"
|
||||
/>
|
||||
<input
|
||||
value={secondaryLight}
|
||||
onChange={(e) => { setSecondaryLight(e.target.value); colorChange(); }}
|
||||
required
|
||||
|
||||
type="color"
|
||||
className="w-10 h-10 rounded-r p-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div className="w-full relative">
|
||||
<label htmlFor="primaryInput">Error Color</label>
|
||||
<div className="flex">
|
||||
<input
|
||||
value={error}
|
||||
required
|
||||
type="text"
|
||||
className="w-full rounded-md rounded-r-none bg-info-bright p-2 text-black shadow-lg"
|
||||
onChange={(e) => { setError(e.target.value); colorChange(); }}
|
||||
placeholder="Choose the primary color"
|
||||
/>
|
||||
<input
|
||||
value={error}
|
||||
onChange={(e) => { setError(e.target.value); colorChange(); }}
|
||||
required
|
||||
|
||||
type="color"
|
||||
className="w-10 h-10 rounded-r p-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full relative">
|
||||
<label htmlFor="primaryInput">Error Dark Color</label>
|
||||
<div className="flex">
|
||||
<input
|
||||
value={errorDark}
|
||||
required
|
||||
type="text"
|
||||
className="w-full rounded-md rounded-r-none bg-info-bright p-2 text-black shadow-lg"
|
||||
onChange={(e) => { setErrorDark(e.target.value); colorChange(); }}
|
||||
placeholder="Choose the primary color"
|
||||
/>
|
||||
<input
|
||||
value={errorDark}
|
||||
onChange={(e) => { setErrorDark(e.target.value); colorChange(); }}
|
||||
required
|
||||
|
||||
type="color"
|
||||
className="w-10 h-10 rounded-r p-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full relative">
|
||||
<label htmlFor="primaryInput">Error Light Color</label>
|
||||
<div className="flex">
|
||||
<input
|
||||
value={errorLight}
|
||||
required
|
||||
type="text"
|
||||
className="w-full rounded-md rounded-r-none bg-info-bright p-2 text-black shadow-lg"
|
||||
onChange={(e) => { setErrorLight(e.target.value); colorChange(); }}
|
||||
placeholder="Choose the primary color"
|
||||
/>
|
||||
<input
|
||||
value={errorLight}
|
||||
onChange={(e) => { setErrorLight(e.target.value); colorChange(); }}
|
||||
required
|
||||
|
||||
type="color"
|
||||
className="w-10 h-10 rounded-r p-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div className="w-full relative">
|
||||
<label htmlFor="primaryInput">Success Color</label>
|
||||
<div className="flex">
|
||||
<input
|
||||
value={success}
|
||||
required
|
||||
type="text"
|
||||
className="w-full rounded-md rounded-r-none bg-info-bright p-2 text-black shadow-lg"
|
||||
onChange={(e) => { setSuccess(e.target.value); colorChange(); }}
|
||||
placeholder="Choose the primary color"
|
||||
/>
|
||||
<input
|
||||
value={success}
|
||||
onChange={(e) => { setSuccess(e.target.value); colorChange(); }}
|
||||
required
|
||||
|
||||
type="color"
|
||||
className="w-10 h-10 rounded-r p-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full relative">
|
||||
<label htmlFor="primaryInput">Success Dark Color</label>
|
||||
<div className="flex">
|
||||
<input
|
||||
value={successDark}
|
||||
required
|
||||
type="text"
|
||||
className="w-full rounded-md rounded-r-none bg-info-bright p-2 text-black shadow-lg"
|
||||
onChange={(e) => { setSuccessDark(e.target.value); colorChange(); }}
|
||||
placeholder="Choose the primary color"
|
||||
/>
|
||||
<input
|
||||
value={successDark}
|
||||
onChange={(e) => { setSuccessDark(e.target.value); colorChange(); }}
|
||||
required
|
||||
|
||||
type="color"
|
||||
className="w-10 h-10 rounded-r p-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full relative">
|
||||
<label htmlFor="primaryInput">Success Light Color</label>
|
||||
<div className="flex">
|
||||
<input
|
||||
value={successLight}
|
||||
required
|
||||
type="text"
|
||||
className="w-full rounded-md rounded-r-none bg-info-bright p-2 text-black shadow-lg"
|
||||
onChange={(e) => { setSuccessLight(e.target.value); colorChange(); }}
|
||||
placeholder="Choose the primary color"
|
||||
/>
|
||||
<input
|
||||
value={successLight}
|
||||
onChange={(e) => { setSuccessLight(e.target.value); colorChange(); }}
|
||||
required
|
||||
|
||||
type="color"
|
||||
className="w-10 h-10 rounded-r p-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PageComponent;
|
||||
|
||||
|
||||
|
124
app/admin/tiers/create/page.tsx
Normal file
@ -0,0 +1,124 @@
|
||||
"use client";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
function PageComponent() {
|
||||
const supabase = createClient();
|
||||
const router = useRouter();
|
||||
const [name, setName] = useState<string>('');
|
||||
const [price, setPrice] = useState<number>(0);
|
||||
const [description, setDescription] = useState<string>('');
|
||||
const [color, setColor] = useState<string>('#000000');
|
||||
|
||||
const getData = async () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const data = {
|
||||
name,
|
||||
price,
|
||||
color,
|
||||
description
|
||||
};
|
||||
const response = await fetch('/api/tiers', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const responseData = await response.json();
|
||||
window.location.href = "/admin/tiers";
|
||||
} else {
|
||||
console.log(response);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full p-8 h-1/2 text-white lg:flex justify-center items-center animate-in">
|
||||
<div className="w-full lg:w-1/3 rounded-md bg-primary opacity-90 p-12 m-1 mt-32 shadow-lg backdrop-blur">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="w-full flex">
|
||||
<span className="text-2xl pb-4">Creating New Tier</span>
|
||||
</div>
|
||||
<div className="w-full flex">
|
||||
<input
|
||||
value={name}
|
||||
required
|
||||
type="text"
|
||||
onChange={(e) => { setName(e.target.value) }}
|
||||
className="mr-2 rounded-md bg-info-bright p-2 w-1/2 text-black shadow-lg"
|
||||
placeholder="Tag Name"
|
||||
/>
|
||||
<div className="flex border border-gray-300 w-1/2 rounded-md">
|
||||
<span className="flex items-center bg-gray-100 rounded-l-md border-r px-3 text-gray-500">
|
||||
$
|
||||
</span>
|
||||
<input
|
||||
value={price}
|
||||
onChange={(e) => { setPrice(Number(e.target.value)) }}
|
||||
required
|
||||
placeholder="Price in USD per month"
|
||||
type="number"
|
||||
className="flex-1 min-w-0 bg-info-bright rounded-r-md px-3 py-2 text-black focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full pt-2 justify-center flex">
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(e) => { setDescription(e.target.value) }}
|
||||
required
|
||||
placeholder="Description of the tier and what the user gets from subscribing."
|
||||
rows={3}
|
||||
className="w-full rounded-md bg-info-bright p-2 w-1/2 text-black shadow-lg"
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="w-full pt-2 justify-center flex">
|
||||
<div className="w-1/2 relative">
|
||||
<input
|
||||
value={color}
|
||||
required
|
||||
type="text"
|
||||
className="w-full rounded-md bg-info-bright p-2 text-black shadow-lg"
|
||||
onChange={(e) => { setColor(e.target.value) }}
|
||||
placeholder="Choose the tiers color"
|
||||
/>
|
||||
<input
|
||||
value={color}
|
||||
onChange={(e) => { setColor(e.target.value) }}
|
||||
required
|
||||
id="colorInput"
|
||||
type="color"
|
||||
className="absolute right-0 top-0 w-10 h-full rounded-r p-1"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="hover:scale-95 ml-2 shadow-lg w-1/2 h-10 text-center bg-success hover:bg-success-light text-white font-bold rounded flex items-center justify-center"
|
||||
>
|
||||
Create
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { router.push("/admin/tiers") }}
|
||||
className="hover:scale-95 shadow-lg ml-2 w-1/2 h-10 text-center bg-error-dark hover:bg-error text-white font-bold rounded flex items-center justify-center"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PageComponent;
|
55
app/admin/tiers/page.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
function PageComponent() {
|
||||
const router = useRouter();
|
||||
const supabase = createClient();
|
||||
const [tiers, setTiers] = useState<any[]>([]);
|
||||
const getData = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/tiers');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setTiers(data);
|
||||
} else {
|
||||
console.error('failed to fetch tiers');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching users:', error);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="w-2/3 p-8 h-1/2 text-white lg:flex justify-center items-center animate-in">
|
||||
<div className="w-full lg:w-1/3 rounded-md bg-primary opacity-90 p-8 m-1 mt-32 shadow-lg backdrop-blur">
|
||||
<div className="w-full flex justify-center">
|
||||
<span className="text-2xl pb-4">Tiers Management</span>
|
||||
</div>
|
||||
<div className="w-full flex justify-center">
|
||||
<button onClick={() => { router.push("/admin/tiers/create") }} className="bg-success hover:bg-success-light rounded p-2 w-full">New Tier</button>
|
||||
<button onClick={() => { router.push("/admin") }} className="bg-error hover:bg-error-light rounded p-2 w-full ml-2">Back</button>
|
||||
</div>
|
||||
<div className="w-full justify-center pt-8">
|
||||
{tiers.map((item, index) => (
|
||||
<div className="w-full flex justify-center">
|
||||
<div key={index} className="text-white w-full text-stroke mt-2">
|
||||
<button onClick={() => { router.push("/admin/tiers/view?name=" + item.name) }} className="py-2 w-full rounded hover:scale-105" style={{ backgroundColor: item.color }}>{item.name} - ${item.price}/month</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PageComponent;
|
||||
|
||||
|
||||
|
141
app/admin/tiers/view/page.tsx
Normal file
@ -0,0 +1,141 @@
|
||||
"use client";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
function PageComponent() {
|
||||
const router = useRouter();
|
||||
const supabase = createClient();
|
||||
const [tier, setTier] = useState<any[]>([]);
|
||||
const [users, setUsers] = useState<any[]>([]);
|
||||
const [name, setName] = useState<string>('');
|
||||
const [price, setPrice] = useState<number>(0);
|
||||
const [description, setDescription] = useState<string>('');
|
||||
const [color, setColor] = useState<string>('#000000');
|
||||
const getData = async () => {
|
||||
try {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const name = searchParams.get('name');
|
||||
const response = await fetch('/api/tiers/'+name);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setTier(data);
|
||||
setName(data.name);
|
||||
setPrice(data.price);
|
||||
setDescription(data.description);
|
||||
setColor(data.color);
|
||||
} else {
|
||||
console.error('failed to fetch tiers');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching users:', error);
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const data = {
|
||||
newName:name,
|
||||
price,
|
||||
color,
|
||||
description
|
||||
};
|
||||
const response = await fetch('/api/tiers/'+name, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const responseData = await response.json();
|
||||
window.location.href = "/admin/tiers";
|
||||
} else {
|
||||
console.log(response);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="w-full p-8 h-1/2 text-white lg:flex justify-center items-center animate-in">
|
||||
<div className="w-full lg:w-1/3 rounded-md bg-primary opacity-90 p-12 m-1 mt-32 shadow-lg backdrop-blur">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="w-full flex">
|
||||
<span className="text-2xl pb-4">Editing Existing Tier</span>
|
||||
</div>
|
||||
<div className="w-full flex">
|
||||
<input
|
||||
value={name}
|
||||
required
|
||||
type="text"
|
||||
onChange={(e) => { setName(e.target.value) }}
|
||||
className="mr-2 rounded-md bg-info-bright p-2 w-1/2 text-black shadow-lg"
|
||||
placeholder="Tag Name"
|
||||
/>
|
||||
<div className="flex border border-gray-300 w-1/2 rounded-md">
|
||||
<span className="flex items-center bg-gray-100 rounded-l-md border-r px-3 text-gray-500">
|
||||
$
|
||||
</span>
|
||||
<input
|
||||
value={price}
|
||||
onChange={(e) => { setPrice(Number(e.target.value)) }}
|
||||
required
|
||||
placeholder="Price in USD per month"
|
||||
type="number"
|
||||
className="flex-1 min-w-0 bg-info-bright rounded-r-md px-3 py-2 text-black focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full pt-2 justify-center flex">
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(e) => { setDescription(e.target.value) }}
|
||||
required
|
||||
placeholder="Description of the tier and what the user gets from subscribing."
|
||||
rows={3}
|
||||
className="w-full rounded-md bg-info-bright p-2 w-1/2 text-black shadow-lg"
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="w-full pt-2 justify-center flex">
|
||||
<div className="w-1/2 relative">
|
||||
<input
|
||||
value={color}
|
||||
required
|
||||
type="text"
|
||||
className="w-full rounded-md bg-info-bright p-2 text-black shadow-lg"
|
||||
onChange={(e) => { setColor(e.target.value) }}
|
||||
placeholder="Choose the tiers color"
|
||||
/>
|
||||
<input
|
||||
value={color}
|
||||
onChange={(e) => { setColor(e.target.value) }}
|
||||
required
|
||||
id="colorInput"
|
||||
type="color"
|
||||
className="absolute right-0 top-0 w-10 h-full rounded-r p-1"
|
||||
/>
|
||||
</div>
|
||||
<button onClick={() => { }} className="hover:scale-95 shadow-lg ml-2 w-1/2 h-10 text-center bg-warning hover:bg-warning-light text-white font-bold rounded flex items-center justify-center">
|
||||
Update
|
||||
</button>
|
||||
<button type="button" onClick={() => { }} className="hover:scale-95 shadow-lg ml-2 w-1/2 h-10 text-center bg-error hover:bg-error-light text-white font-bold rounded flex items-center justify-center">
|
||||
Delete
|
||||
</button>
|
||||
<button type="button" onClick={() => { router.push("/admin/tiers") }} className="hover:scale-95 shadow-lg ml-2 w-1/2 h-10 text-center bg-error-dark hover:bg-error text-white font-bold rounded flex items-center justify-center">
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PageComponent;
|
||||
|
||||
|
||||
|
26
app/api/admin/interface-configs/route.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
|
||||
export async function PUT(request: Request) {
|
||||
const body = await request.json();
|
||||
console.log(body)
|
||||
const supabase = createClient();
|
||||
var value = body.value;
|
||||
const { error } = await supabase.from('interface_configurations').update({ value }).eq('name', body.name);
|
||||
console.log(error)
|
||||
if (error) {
|
||||
return NextResponse.error();
|
||||
}
|
||||
return NextResponse.json({});
|
||||
}
|
||||
|
||||
export async function GET(request: Request) {
|
||||
|
||||
const url = new URL(request.url as string, 'http://localhost');
|
||||
const name = url.searchParams.get('name') || '';
|
||||
const supabase = createClient();
|
||||
const { data, error } = await supabase.from('interface_configurations').select('*').eq('name', name).single();
|
||||
return NextResponse.json(data);
|
||||
}
|
17
app/api/admin/users/route.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const supabase = createClient();
|
||||
const { data, error } = await supabase.auth.admin.listUsers();
|
||||
console.log(error)
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
return NextResponse.error();
|
||||
}
|
||||
}
|
27
app/api/galleries/[id]/images/count/route.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
const galleryId = params.id;
|
||||
const supabase = createClient();
|
||||
|
||||
const { data: gallery, error: galleryError } = await supabase
|
||||
.from('galleries')
|
||||
.select('*')
|
||||
.eq('name', galleryId)
|
||||
.single();
|
||||
// List all files in the galleryId path
|
||||
let { data: files, error } = await supabase.storage.from('galleries').list(params.id.toLowerCase().replace(/\s+/g, '_'));
|
||||
|
||||
if (files==null || error) {
|
||||
//console.error('Error listing files:', error);
|
||||
return NextResponse.error();
|
||||
}
|
||||
|
||||
|
||||
// Return a JSON response with the array of URLs
|
||||
return new Response(JSON.stringify(files.length), { headers: { 'content-type': 'application/json' } });
|
||||
}
|
34
app/api/galleries/[id]/images/names/route.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
const galleryId = params.id;
|
||||
const supabase = createClient();
|
||||
|
||||
const { data: gallery, error: galleryError } = await supabase
|
||||
.from('galleries')
|
||||
.select('*')
|
||||
.eq('name', galleryId)
|
||||
.single();
|
||||
|
||||
// List all files in the galleryId path
|
||||
let { data: files, error } = await supabase.storage
|
||||
.from('galleries')
|
||||
.list(params.id.toLowerCase().replace(/\s+/g, '_'));
|
||||
|
||||
if (files == null || error) {
|
||||
//console.error('Error listing files:', error);
|
||||
return NextResponse.error();
|
||||
}
|
||||
|
||||
// Extract file names from the list of files
|
||||
const fileNames = files.map((file) => file.name);
|
||||
|
||||
// Return a JSON response with the array of file names
|
||||
return new Response(JSON.stringify(fileNames), {
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
@ -1,18 +1,38 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import sharp from 'sharp';
|
||||
|
||||
|
||||
async function blurImage(blob: Buffer): Promise<Buffer> {
|
||||
// Convert the blob to a sharp object
|
||||
const image = sharp(blob);
|
||||
|
||||
// Blur the image
|
||||
const blurredImage = await image.blur(75).toBuffer();
|
||||
|
||||
return blurredImage;
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
const galleryId = params.id;
|
||||
const supabase = createClient();
|
||||
|
||||
// List all files in the galleryId path
|
||||
const galleryId = params.id; const supabase = createClient();
|
||||
const user = await supabase.auth.getUser();
|
||||
|
||||
|
||||
const { data: gallery, error: galleryError } = await supabase
|
||||
.from('galleries')
|
||||
.select('*')
|
||||
.eq('name', params.id)
|
||||
.single();
|
||||
|
||||
// List all files in the params.id path
|
||||
let { data: files, error } = await supabase.storage.from('galleries').list(galleryId);
|
||||
|
||||
if (files==null || error) {
|
||||
console.error('Error listing files:', error);
|
||||
//console.error('Error listing files:', error);
|
||||
return NextResponse.error();
|
||||
}
|
||||
|
||||
@ -23,13 +43,38 @@ export async function GET(
|
||||
let { data: blobdata, error } = await supabase.storage.from('galleries').download(galleryId+"/"+file.name);
|
||||
|
||||
if (error || blobdata==null) {
|
||||
console.error('Error downloading file:', error);
|
||||
//console.error('Error downloading file:', error);
|
||||
continue;
|
||||
}
|
||||
let blobBuffer = Buffer.from(await blobdata.arrayBuffer());
|
||||
|
||||
const base64 = Buffer.from(await blobdata.arrayBuffer()).toString('base64');
|
||||
let userId = user.data.user?.id;
|
||||
let { data: subscription, error: rolesError } = await supabase
|
||||
.from('user_subscriptions')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.single();
|
||||
switch(gallery.tier){
|
||||
case "Tier 3":
|
||||
if(subscription?.tier!="Tier 3"){
|
||||
blobBuffer = await blurImage(blobBuffer);
|
||||
}
|
||||
break;
|
||||
case "Tier 2":
|
||||
if(subscription?.tier!="Tier 3" && subscription?.tier!="Tier 2"){
|
||||
blobBuffer = await blurImage(blobBuffer);
|
||||
}
|
||||
break;
|
||||
case "Tier 1":
|
||||
if(subscription?.tier!="Tier 3" && subscription?.tier!="Tier 2" && subscription?.tier!="Tier 1"){
|
||||
blobBuffer = await blurImage(blobBuffer);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const contentType = file.name.endsWith('.png') ? 'image/png' : 'image/jpeg';
|
||||
const dataUrl = `data:${contentType};base64,${base64}`;
|
||||
const dataUrl = `data:${contentType};base64,${blobBuffer.toString('base64')}`;
|
||||
|
||||
urls.push(dataUrl);
|
||||
}
|
||||
|
@ -1,29 +1,64 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import sharp from 'sharp';
|
||||
|
||||
|
||||
async function blurImage(blob: Buffer): Promise<Buffer> {
|
||||
// Convert the blob to a sharp object
|
||||
const image = sharp(blob);
|
||||
|
||||
// Blur the image
|
||||
const blurredImage = await image.blur(75).toBuffer();
|
||||
|
||||
return blurredImage;
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
const galleryId= params.id // 312
|
||||
) {
|
||||
const supabase = createClient();
|
||||
const user = await supabase.auth.getUser();
|
||||
const url = new URL(request.url);
|
||||
const search = url.searchParams.get("nsfw");
|
||||
const nsfw = search === "true";
|
||||
|
||||
// Extract galleryId from the route value
|
||||
var blob = null;
|
||||
var contentType = "image/jpeg"
|
||||
let { data: blobdata, error } = await supabase.storage.from('galleries').download(galleryId+'/1.jpeg')
|
||||
blob = blobdata;
|
||||
console.log(error)
|
||||
if (error) {
|
||||
contentType = "image/png"
|
||||
let { data: blobdata, error } = await supabase.storage.from('galleries').download(galleryId+'/1.png')
|
||||
console.log(error)
|
||||
blob = blobdata;
|
||||
}
|
||||
if(blob != null){
|
||||
const base64 = Buffer.from(await blob.arrayBuffer()).toString('base64');
|
||||
const dataUrl = `data:${contentType};base64,${base64}`;
|
||||
return new Response(dataUrl);
|
||||
}
|
||||
const { data: gallery, error: galleryError } = await supabase
|
||||
.from('galleries')
|
||||
.select('*')
|
||||
.eq('name', params.id)
|
||||
.single();
|
||||
|
||||
var thumbnailFile = gallery.thumbnail_file;
|
||||
if(thumbnailFile==null || thumbnailFile==""){
|
||||
let { data: files, error } = await supabase.storage.from('galleries').list(params.id);
|
||||
if (files == null || files?.length == 0) {
|
||||
return NextResponse.error();
|
||||
}
|
||||
thumbnailFile = files[0].name;
|
||||
}
|
||||
|
||||
// Loop through each file, download it, convert it to base64, and add the data URL to the array
|
||||
let { data: blobdata, error: fileError } = await supabase.storage.from('galleries').download(params.id + "/" + thumbnailFile);
|
||||
if (fileError || blobdata == null) {
|
||||
//console.error('Error downloading file:', error);
|
||||
return NextResponse.error();
|
||||
}
|
||||
let blobBuffer = Buffer.from(await blobdata.arrayBuffer());
|
||||
|
||||
let userId = user.data.user?.id;
|
||||
let { data: subscription, error: rolesError } = await supabase
|
||||
.from('user_subscriptions')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.single();
|
||||
if(nsfw && gallery.nsfw){
|
||||
blobBuffer = await blurImage(blobBuffer);
|
||||
}
|
||||
const contentType = thumbnailFile.endsWith('.png') ? 'image/png' : 'image/jpeg';
|
||||
const dataUrl = `data:${contentType};base64,${blobBuffer.toString('base64')}`;
|
||||
|
||||
|
||||
// Return a JSON response with the array of URLs
|
||||
return new Response(dataUrl);
|
||||
}
|
110
app/api/galleries/admin/[id]/route.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import path from 'path';
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
const id = params.id;
|
||||
const supabase = createClient();
|
||||
const { data: gallery, error } = await supabase.from('galleries').select("*").eq('name', id).single();
|
||||
return NextResponse.json({ gallery });
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function PUT(
|
||||
request: Request,
|
||||
{ params }: { params: { id: string } }){
|
||||
|
||||
const supabase = createClient();
|
||||
const formData = await request.formData();
|
||||
const tags = JSON.parse(formData.getAll('tags').toString()) as string[];
|
||||
const originalName = formData.get('originalName');
|
||||
const name = formData.get('name')?.toString();
|
||||
const nsfw = formData.get('nsfw')?.toString();
|
||||
const tier = formData.get('tier')?.toString();
|
||||
const thumbnail = formData.get('thumbnail');
|
||||
|
||||
const { error } = await supabase.from('galleries').update({name, tags, nsfw, tier, thumbnail_file:thumbnail}).eq('name', originalName ?? '');
|
||||
|
||||
async function renameFolder(oldFolderName: any, newFolderName: string) {
|
||||
// Get a list of all files in the old folder
|
||||
let { data: oldFiles, error } = await supabase.storage.from('galleries').list(oldFolderName);
|
||||
if (error) {
|
||||
console.error('Error fetching files:', error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Move each file to the new folder
|
||||
if (oldFiles) {
|
||||
for (let file of oldFiles) {
|
||||
let oldPath = file.name;
|
||||
let newPath = newFolderName + '/' + oldPath.split('/').pop();
|
||||
|
||||
let { error: moveError } = await supabase.storage.from('galleries').move(oldPath, newPath);
|
||||
if (moveError) {
|
||||
console.error(`Error moving file ${oldPath} to ${newPath}:`, moveError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the old folder
|
||||
let { error: deleteError } = await supabase.storage.from('galleries').remove([oldFolderName]);
|
||||
if (deleteError) {
|
||||
console.error('Error deleting old folder:', deleteError);
|
||||
}
|
||||
}
|
||||
|
||||
renameFolder(originalName, name ?? '');
|
||||
|
||||
if(error){
|
||||
return NextResponse.error();
|
||||
}
|
||||
let { data: galleries, error:galleriesError } = await supabase
|
||||
.from('galleries')
|
||||
.select('*');
|
||||
return NextResponse.json({ success: true, galleries });
|
||||
}
|
||||
|
||||
|
||||
export async function DELETE(
|
||||
request: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
const id =params.id;
|
||||
const supabase = createClient();
|
||||
const { data: gallery, error } = await supabase.from('galleries').delete().eq('name', id).single();
|
||||
let { data: galleries, error:galleriesError } = await supabase
|
||||
.from('galleries')
|
||||
.select('*');
|
||||
return NextResponse.json({ success: true, galleries });
|
||||
}
|
||||
|
||||
// const tagsResponse = await fetch(`/api/galleries/tags?search=${search}`);
|
||||
// const tagsData = await tagsResponse.json();
|
||||
// const galleriesWithTagData = galleriesData.map((gallery: any) => {
|
||||
// const tags = tagsData.filter((tag: any) => gallery.tags.includes(tag.name));
|
||||
// return { ...gallery, tags };
|
||||
// });
|
||||
|
||||
|
||||
// const formData = new FormData();
|
||||
// formData.append('name', name);
|
||||
// formData.append('tags', JSON.stringify(tags));
|
||||
// files.forEach((file: File) => {
|
||||
// formData.append('files', file);
|
||||
// });
|
||||
|
||||
// const response = await fetch('/api/galleries', {
|
||||
// method: 'POST',
|
||||
// body: formData,
|
||||
// });
|
||||
|
||||
// if (response.ok) {
|
||||
// const data = await response.json();
|
||||
// // Handle success
|
||||
// } else {
|
||||
// // Handle error
|
||||
// }
|
37
app/api/galleries/admin/route.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import path from 'path';
|
||||
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const supabase = createClient();
|
||||
let { data: galleries, error } = await supabase
|
||||
.from('galleries')
|
||||
.select('*');
|
||||
return NextResponse.json(galleries);
|
||||
}
|
||||
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const supabase = createClient();
|
||||
const formData = await request.formData();
|
||||
const files = formData.getAll('files');
|
||||
const tags = JSON.parse(formData.getAll('tags').toString()) as string[];
|
||||
const name = formData.get('name');
|
||||
const nsfw = formData.get('nsfw');
|
||||
const tier = formData.get('tier');
|
||||
const thumbnail = formData.get('thumbnail');
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i] as File; // Cast 'file' to 'File' type
|
||||
supabase.storage.from('galleries').upload(`${name}/${file.name}`, file);
|
||||
}
|
||||
const { data: gallery, error } = await supabase.from('galleries').insert({ name, tags, nsfw, thumbnail_file:thumbnail, tier, column_number: 3 }).single();
|
||||
|
||||
let { data: galleries, error: galleriesError } = await supabase
|
||||
.from('galleries')
|
||||
.select('*');
|
||||
|
||||
return NextResponse.json({ success: true, galleries });
|
||||
}
|
||||
|
@ -2,11 +2,34 @@ import { NextResponse } from "next/server";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
|
||||
|
||||
export async function GET(request: Request) {
|
||||
export async function POST(request: Request) {
|
||||
const supabase = createClient();
|
||||
const url = new URL(request.url);
|
||||
const search = url.searchParams.get("search");
|
||||
const data = await request.json();
|
||||
const tags = data.tags as string[];
|
||||
if(tags.length === 0){
|
||||
let { data: galleries, error } = await supabase
|
||||
.from('galleries')
|
||||
.select('*')
|
||||
return NextResponse.json(galleries)
|
||||
.ilike('name', `%${search}%`)
|
||||
return NextResponse.json(galleries);
|
||||
}
|
||||
else{
|
||||
// Rest of the code...
|
||||
let { data: galleries, error } = await supabase
|
||||
.from('galleries')
|
||||
.select('*')
|
||||
.contains('tags', tags) // Fix: Use contains instead of overlaps
|
||||
.ilike('name', `%${search}%`)
|
||||
|
||||
return NextResponse.json(galleries);
|
||||
}
|
||||
}
|
||||
|
||||
// const tagsResponse = await fetch(`/api/galleries/tags?search=${search}`);
|
||||
// const tagsData = await tagsResponse.json();
|
||||
// const galleriesWithTagData = galleriesData.map((gallery: any) => {
|
||||
// const tags = tagsData.filter((tag: any) => gallery.tags.includes(tag.name));
|
||||
// return { ...gallery, tags };
|
||||
// });
|
32
app/api/galleries/tags/route.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const supabase = createClient();
|
||||
let { data: tags, error } = await supabase
|
||||
.from('tags')
|
||||
.select('*')
|
||||
.order('name', { ascending: true });
|
||||
return NextResponse.json(tags)
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const supabase = createClient();
|
||||
const data = await request.json();
|
||||
const { data: tag, error } = await supabase.from('tags').insert({ name: data.tag }).single();
|
||||
if (error) {
|
||||
return NextResponse.error();
|
||||
}
|
||||
return NextResponse.json(tag);
|
||||
}
|
||||
|
||||
export async function PUT(request: Request) {
|
||||
const supabase = createClient();
|
||||
const data = await request.json();
|
||||
const { data: tag, error } = await supabase.from('tags').delete().eq('name', data.tag).single();
|
||||
if (error) {
|
||||
return NextResponse.error();
|
||||
}
|
||||
return NextResponse.json(tag);
|
||||
}
|
36
app/api/tiers/[name]/route.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { name: string } }
|
||||
) {
|
||||
const supabase = createClient();
|
||||
|
||||
const { data: tier, error: galleryError } = await supabase
|
||||
.from('tiers')
|
||||
.select('*')
|
||||
.eq('name', params.name)
|
||||
.single();
|
||||
|
||||
if(galleryError) {
|
||||
return NextResponse.error();
|
||||
}
|
||||
|
||||
return NextResponse.json(tier);
|
||||
}
|
||||
export async function PUT(
|
||||
request: Request,
|
||||
{ params }: { params: { name: string } }
|
||||
) {
|
||||
const supabase = createClient();
|
||||
const { newName, price, color, description } = await request.json();
|
||||
console.log(newName)
|
||||
const { error } = await supabase.from('tiers')
|
||||
.update({ name:newName, price, color, description }).eq('name', params.name);
|
||||
if (error) {
|
||||
console.error('Error updating tier:', error);
|
||||
return NextResponse.error();
|
||||
}
|
||||
return NextResponse.json({});
|
||||
}
|
26
app/api/tiers/route.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const supabase = createClient();
|
||||
const { data, error } = await supabase.from('tiers').select('*');
|
||||
if (error) {
|
||||
console.error('Error fetching tiers:', error);
|
||||
return NextResponse.error();
|
||||
}
|
||||
const tiers = data ?? [];
|
||||
return NextResponse.json(tiers);
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const supabase = createClient();
|
||||
const { name, price, color, description } = await request.json();
|
||||
const { data, error } = await supabase.from('tiers').insert([{ name, price, color, description }]);
|
||||
if (error) {
|
||||
console.error('Error inserting tier:', error);
|
||||
return NextResponse.error();
|
||||
}
|
||||
return NextResponse.json(data);
|
||||
}
|
||||
|
168
app/gallery/admin/create/page.tsx
Normal file
@ -0,0 +1,168 @@
|
||||
"use client";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Masonry from "react-masonry-css";
|
||||
import SearchInput from "@/components/neroshitron/search_input";
|
||||
|
||||
function PageComponent() {
|
||||
const router = useRouter();
|
||||
const [selectedGallery, setSelectedGallery] = useState<string | null>(null);
|
||||
const [filePreviews, setFilePreviews] = useState<string[]>([]);
|
||||
const [name, setName] = useState<string>('');
|
||||
const [nsfw, setNsfw] = useState<boolean>(false);
|
||||
const [tags, setTags] = useState<string[]>([]);
|
||||
const [tier, setTier] = useState<string>('Free');
|
||||
const [thumbnail, setThumbnail] = useState<string>("");
|
||||
const [files, setFiles] = useState<FileList>();
|
||||
|
||||
const [tiers, setTiers] = useState<any[]>([]);
|
||||
const supabase = createClient();
|
||||
const user = supabase.auth.getUser();
|
||||
const getData = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/tiers');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setTiers(data);
|
||||
} else {
|
||||
console.error('failed to fetch tiers');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching users:', error);
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, [selectedGallery]);
|
||||
|
||||
const closeGallery = () => {
|
||||
setSelectedGallery(null);
|
||||
}
|
||||
|
||||
const createGallery = async () => {
|
||||
const formData = new FormData();
|
||||
formData.append('name', name);
|
||||
if (files) {
|
||||
Array.from(files).forEach((file: File) => {
|
||||
formData.append('files', file);
|
||||
});
|
||||
}
|
||||
formData.append('tags', JSON.stringify(tags));
|
||||
formData.append('nsfw', nsfw.toString());
|
||||
formData.append('thumbnail', thumbnail);
|
||||
formData.append('tier', tier);
|
||||
const response = await fetch('/api/galleries/admin', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
window.location.href = "/gallery/admin/view?id="+name;
|
||||
} else {
|
||||
console.log(response)
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = event.target.files;
|
||||
if (files) {
|
||||
const previews: string[] = [];
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
if (e.target && e.target.result) {
|
||||
previews.push(e.target.result.toString());
|
||||
if (previews.length === files.length) {
|
||||
setFiles(files);
|
||||
setFilePreviews(previews);
|
||||
}
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full p-8 text-white flex justify-center items-center animate-in">
|
||||
<div className="w-full lg:w-1/2 rounded-md bg-primary opacity-90 backdrop-blur-lg p-12 mt-32 shadow-lg">
|
||||
<div className="w-full flex pb-4">
|
||||
<span className="text-2xl">Creating New Gallery</span>
|
||||
</div>
|
||||
<div className="w-full flex">
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="mb-8 mr-2 rounded-md bg-secondary p-2 w-1/2 text-white shadow-lg"
|
||||
placeholder="Gallery Name"
|
||||
/>
|
||||
<div className="w-1/4">
|
||||
<button onClick={() => router.push("/gallery/admin")} className="w-full bg-error hover:bg-error-light text-white rounded-md p-2 shadow-lg">
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
<div className="w-1/4">
|
||||
<button onClick={()=>{createGallery()}} className="w-full bg-success hover:bg-success-light text-white rounded-md p-2 ml-2 shadow-lg ">
|
||||
<span></span>Create
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full lg:flex">
|
||||
<div className="w-full lg:w-1/2">
|
||||
<div className="w-1/2 absolute pr-14">
|
||||
<SearchInput
|
||||
placeholderTags={[
|
||||
{ value: "tags", label: "❗️ click here to add tags" },
|
||||
]}
|
||||
startingTags={tags}
|
||||
nsfwButtonEnabled={true}
|
||||
searchChanged={(search) => { }}
|
||||
nsfwChanged={(nsfw) => { }}
|
||||
tagsChanged={(tags) => { setTags(tags) }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full lg:w-1/2 lg:pt-0 pt-4">
|
||||
<select value={nsfw ? "NSFW" : "SFW"} className="mb-2 shadow-lg rounded-md bg-secondary p-2 w-full text-white" onChange={e=>{
|
||||
setNsfw(e.target.value === "NSFW");
|
||||
}}>
|
||||
<option value="NSFW" selected={nsfw}>NSFW</option>
|
||||
<option value="SFW" selected={nsfw}>SFW</option>
|
||||
</select>
|
||||
<select onChange={e=>{
|
||||
setTier(e.target.value);
|
||||
}} className="mb-2 shadow-lg mr-2 rounded-md bg-secondary p-2 w-full text-white">
|
||||
<option value="" disabled >Select New Tier</option>
|
||||
{tiers.map((tier, index) => (
|
||||
<option key={index} value={tier.name}>{tier.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<select onChange={e=>{setThumbnail(e.target.value)}} className="mb-2 shadow-lg mr-2 rounded-md bg-secondary p-2 w-full text-white">
|
||||
<option value="" disabled selected>Select Thumbnail</option>
|
||||
{files && Array.from(files).map((file: File, index: number) => (
|
||||
<option key={index} value={file.name}>{file.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<input
|
||||
className="relative m-0 block w-full min-w-0 flex-auto cursor-pointer rounded border border-solid border-secondary-lighter bg-transparent bg-clip-padding px-3 py-[0.32rem] text-base font-normal text-surface transition duration-300 ease-in-out file:-mx-3 file:-my-[0.32rem] file:me-3 file:cursor-pointer file:overflow-hidden file:rounded-none file:border-0 file:border-e file:border-solid file:border-inherit file:bg-transparent file:px-3 file:py-[0.32rem] file:text-surface focus:border-primary focus:text-gray-700 focus:shadow-inset focus:outline-none dark:border-white/70 dark:text-white file:dark:text-white"
|
||||
type="file"
|
||||
id="formFileMultiple"
|
||||
multiple
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
<Masonry breakpointCols={3} className="my-masonry-grid pl-6 col-span-2">
|
||||
{filePreviews.map((preview, index) => (
|
||||
<img key={index} src={preview} alt={`Preview ${index}`} />
|
||||
))}
|
||||
</Masonry>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default PageComponent;
|
161
app/gallery/admin/page.tsx
Normal file
@ -0,0 +1,161 @@
|
||||
"use client";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import SearchInput from "@/components/neroshitron/search_input";
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
function PageComponent() {
|
||||
const router = useRouter();
|
||||
const supabase = createClient();
|
||||
const user = supabase.auth.getUser();
|
||||
const [tags, setTags] = useState<any[]>([]);
|
||||
const [nsfwState, setNsfwState] = useState<boolean>(false);
|
||||
const [tagsState, setTagsState] = useState<string[]>([]);
|
||||
const [searchState, setSearchState] = useState<string>("");
|
||||
const [galleries, setGalleries] = useState([]);
|
||||
const [tagSearch, setTagSearch] = useState<string>('');
|
||||
const [newTagName, setNewTagName] = useState<string>('');
|
||||
const getData = async () => {
|
||||
const tagsResponse = await fetch(`/api/galleries/tags`);
|
||||
const tagsData = await tagsResponse.json();
|
||||
setTags(tagsData);
|
||||
const galleriesResponse = await fetch(`/api/galleries?search=` + searchState + '&nsfw=' + nsfwState, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ tags: tagsState })
|
||||
});
|
||||
const galleriesData = await galleriesResponse.json();
|
||||
setGalleries(galleriesData);
|
||||
}
|
||||
|
||||
const createTag = async () => {
|
||||
let formattedTag = newTagName.toLowerCase().replace(" ", "_");
|
||||
const tagsResponse = await fetch(`/api/galleries/tags`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ tag: formattedTag })
|
||||
});
|
||||
const tagsData = await tagsResponse.json();
|
||||
setNewTagName('');
|
||||
getData();
|
||||
}
|
||||
|
||||
const deleteTag = async (tagParam: string) => {
|
||||
const tagsResponse = await fetch(`/api/galleries/tags`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ tag:tagParam })
|
||||
});
|
||||
const tagsData = await tagsResponse.json();
|
||||
getData();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, [tagsState, newTagName]);
|
||||
|
||||
|
||||
return (
|
||||
<div className="w-full p-8 h-max text-white lg:flex justify-center items-start animate-in mt-32">
|
||||
<div className="w-full h-max lg:w-1/3 rounded-md bg-primary opacity-90 p-8 m-1 shadow-lg backdrop-blur">
|
||||
<span className="text-2xl">Tags Management</span>
|
||||
<div className="w-full flex pt-4">
|
||||
<form onSubmit={createTag} className="flex w-full">
|
||||
|
||||
<input value={newTagName} required type="text" onChange={(e)=>{setNewTagName(e.target.value)}} className=" mb-8 mr-2 rounded-md bg-info-bright p-2 w-1/2 text-black shadow-lg" placeholder="Tag Name" />
|
||||
<button className=" ml-2 shadow-lg w-1/2 h-10 text-center bg-success hover:bg-success-light text-white font-bold rounded flex items-center justify-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="lg:hidden size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
|
||||
<span className="lg:hidden block">Tag</span>
|
||||
<span className="lg:block hidden">New Tag</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="w-full flex">
|
||||
<input type="text" value={tagSearch} onChange={(e)=>{setTagSearch(e.target.value)}} className=" mb-8 shadow-lg mr-2 rounded-md bg-info-bright p-2 w-full text-black" placeholder="Search all tags by name" />
|
||||
</div>
|
||||
<div className="w-full h-96 overflow-y-scroll no-scrollbar">
|
||||
<table className="w-full bg-primary-light rounded">
|
||||
<tbody>
|
||||
{tags.filter((value,index,array)=>{
|
||||
return value.name.toLowerCase().includes(tagSearch.toLowerCase());
|
||||
}).map((item:any) => (
|
||||
<tr key={item.name} className="hover:bg-secondary-dark animate-in shadow">
|
||||
<td className="px-4 py-2">{item.name}</td>
|
||||
<td className="px-4 py-2">
|
||||
<button onClick={()=>{deleteTag(item.name)}} className=" bg-error shadow-lg hover:bg-error-light text-white font-bold py-2 px-4 rounded float-right">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
||||
</svg>
|
||||
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full h-max lg:w-1/2 rounded-md bg-primary opacity-90 backdrop-blur-lg p-8 m-1 shadow-lg">
|
||||
<div className="w-full pb-2 flex justify-between">
|
||||
<span className="text-2xl">Galleries Management</span>
|
||||
<div>
|
||||
<button onClick={()=>{router.push("/admin/")}} className="ml-2 p-2 shadow-lg h-10 text-center bg-error hover:bg-error-light text-white font-bold rounded">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="xl:hidden size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
|
||||
<span className="xl:block hidden">Back</span>
|
||||
</button>
|
||||
<button onClick={()=>{router.push("/gallery/admin/create")}} className="ml-2 p-2 shadow-lg h-10 text-center bg-success hover:bg-success-light text-white font-bold rounded">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="xl:hidden size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
|
||||
<span className="xl:block hidden">New Gallery</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full pb-2 flex">
|
||||
<div className="absolute w-full z-20 pr-16">
|
||||
<SearchInput
|
||||
startingTags={[]}
|
||||
placeholderTags={[
|
||||
{ value: "tags", label: "❗️ click here to add tags to search" }
|
||||
]} nsfwButtonEnabled={false} searchChanged={(search) => { setSearchState(search) }} nsfwChanged={(nsfw) => { setNsfwState(nsfw) }} tagsChanged={(tags) => { setTagsState(tags) }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full h-96 overflow-y-scroll no-scrollbar">
|
||||
<table className="w-full mt-20 bg-primary-light rounded">
|
||||
<tbody>
|
||||
{/* Replace this with your data mapping logic */}
|
||||
{galleries.map((item: { name: string, imageCount: number, tier: string }) => (
|
||||
<tr key={item.name} className="hover:bg-secondary-dark shadow animate-in">
|
||||
<td className="px-4 py-2" style={{ width: '65%' }}>{item.name}</td>
|
||||
<td className="px-4 py-2">{item.imageCount}</td>
|
||||
<td className="px-4 py-2">{item.tier}</td>
|
||||
<td className="px-4 py-2">
|
||||
<button onClick={()=>{ router.push(`/gallery/admin/view?id=${encodeURIComponent(item.name)}`)}} className="bg-secondary shadow-lg hover:bg-secondary-light text-white font-bold py-2 px-4 rounded float-right">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m5.231 13.481L15 17.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v16.5c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Zm3.75 11.625a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default PageComponent;
|
271
app/gallery/admin/view/page.tsx
Normal file
@ -0,0 +1,271 @@
|
||||
"use client";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Gallery from "@/components/neroshitron/gallery";
|
||||
import SearchInput from "@/components/neroshitron/search_input";
|
||||
import GalleryThumbnail from "@/components/neroshitron/gallery_thumbnail";
|
||||
|
||||
function PageComponent() {
|
||||
const [filePreviews, setFilePreviews] = useState<string[]>([]);
|
||||
const supabase = createClient();
|
||||
const user = supabase.auth.getUser();
|
||||
const [gallery , setGallery] = useState<any>(null);
|
||||
const [originalName, setOriginalGalleryName] = useState<string>('');
|
||||
const [galleryName, setGalleryName] = useState<string>('');
|
||||
const [nsfw, setNsfw] = useState<boolean>(false);
|
||||
const [tags, setTags] = useState<string[]>([]);
|
||||
const [tier, setTier] = useState<string>('Free');
|
||||
const [thumbnail, setThumbnail] = useState<string>();
|
||||
const [fileNames, setFileNames] = useState<string[]>([]);
|
||||
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||
const router = useRouter();
|
||||
|
||||
const [tiers, setTiers] = useState<any[]>([]);
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
|
||||
|
||||
const [images, setImages] = useState<string[]>([]);
|
||||
const getData = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/tiers');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setTiers(data);
|
||||
} else {
|
||||
console.error('failed to fetch tiers');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching users:', error);
|
||||
}
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const id = urlParams.get('id');
|
||||
const galleryResponse = await fetch(`/api/galleries/admin/${id}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const galleryData = await galleryResponse.json();
|
||||
setGallery(galleryData.gallery);
|
||||
|
||||
const filesResponse = await fetch(`/api/galleries/${id}/images/names`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const filesData = await filesResponse.json();
|
||||
setFileNames(filesData);
|
||||
|
||||
setNsfw(galleryData.gallery.nsfw);
|
||||
setTags(galleryData.gallery.tags);
|
||||
setTier(galleryData.gallery.tier);
|
||||
setGalleryName(galleryData.gallery.name);
|
||||
if(originalName === ''){
|
||||
setOriginalGalleryName(galleryData.gallery.name);
|
||||
}
|
||||
const imagesResponse = await fetch('/api/galleries/' + galleryData.gallery.name+ '/images');
|
||||
const imagesUrls = await imagesResponse.json() as string[];
|
||||
setImages(imagesUrls);
|
||||
}
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
}, [gallery]);
|
||||
|
||||
useEffect(() => {
|
||||
}, [gallery, ]);
|
||||
useEffect(() => {
|
||||
}, [ nsfw ]);
|
||||
useEffect(() => {
|
||||
}, [tags ]);
|
||||
useEffect(() => {
|
||||
}, [galleryName]);
|
||||
useEffect(() => {
|
||||
}, [ tier]);
|
||||
|
||||
const updateGallery = async () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const id = urlParams.get('id');
|
||||
const formData = new FormData();
|
||||
formData.append('id', gallery.name);
|
||||
formData.append('name', galleryName);
|
||||
formData.append('thumbnail', thumbnail ?? '');
|
||||
formData.append('originalName', originalName);
|
||||
formData.append('tags', JSON.stringify(selectedTags));
|
||||
formData.append('nsfw', nsfw.toString());
|
||||
formData.append('tier', tier);
|
||||
const response = await fetch(`/api/galleries/admin/${originalName}`, {
|
||||
method: 'PUT',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
console.log(response)
|
||||
const data = await response.json();
|
||||
} else {
|
||||
console.log(response)
|
||||
}
|
||||
if(originalName != galleryName){
|
||||
router.push(`/gallery/admin/view?id=${galleryName}`)
|
||||
}
|
||||
else{
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
const deleteGallery = async () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const id = urlParams.get('id');
|
||||
const response = await fetch(`/api/gallery/admin/${gallery.name}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
router.push("/gallery/admin");
|
||||
} else {
|
||||
console.log(response)
|
||||
}
|
||||
}
|
||||
|
||||
const tierObj = tiers.find((tier) => tier.name == gallery.tier);
|
||||
const subscriptionColor = tier ? tierObj.color : null;
|
||||
return (
|
||||
<div className="w-full p-8 h-screen text-white flex justify-center items-center animate-in">
|
||||
<div className="w-full lg:w-1/2 rounded-md p-12 mt-14 ">
|
||||
<div className="w-full lg:pt-0 pt-32 flex pb-60 justify-center"> {/* Center the gallery thumbnail */}
|
||||
{gallery != null && (
|
||||
<GalleryThumbnail
|
||||
key={"galleryThumbnail"+galleryName+"-"+tags.join("")}
|
||||
id={originalName}
|
||||
columns={3}
|
||||
onSelect={function (id: string, columns: number): void { setOpen(true) }}
|
||||
title={galleryName}
|
||||
subscription={tier}
|
||||
tags={tags}
|
||||
subscriptionColor={subscriptionColor}
|
||||
showNsfw={false}
|
||||
nsfw={nsfw}
|
||||
></GalleryThumbnail>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-full opacity-90 backdrop-blur-lg bg-primary shadow-lg p-8 pb-0 rounded">
|
||||
<span className="text-2xl">Editing Gallery</span>
|
||||
<div className="w-full flex justify-end">
|
||||
<div className="w-1/2 flex">
|
||||
<input
|
||||
type="text"
|
||||
className="mb-8 mr-2 rounded-md bg-secondary p-2 w-full text-white"
|
||||
placeholder="Gallery Name"
|
||||
value={galleryName}
|
||||
onChange={(e) => setGalleryName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-1/2 flex">
|
||||
<button
|
||||
onClick={() => deleteGallery()}
|
||||
className="h-10 text-center w-full bg-error hover:bg-error-light text-white rounded-md p-2 flex items-center justify-center"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5}
|
||||
stroke="currentColor" className="md:hidden size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
||||
</svg>
|
||||
|
||||
<span className="md:block hidden">Delete</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => (router.push("/gallery/admin"))}
|
||||
className="h-10 w-full bg-error-dark hover:bg-error text-white rounded-md p-2 ml-2 flex items-center justify-center"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5}
|
||||
stroke="currentColor" className="md:hidden size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 15 3 9m0 0 6-6M3 9h12a6 6 0 0 1 0 12h-3" />
|
||||
</svg>
|
||||
|
||||
<span className="md:block hidden">Back</span>
|
||||
</button>
|
||||
<button onClick={()=>{updateGallery()}} className="h-10 w-full bg-warning hover:bg-warning-light text-white rounded-md p-2 ml-2">
|
||||
<span>Update</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full lg:flex opacity-90 backdrop-blur-lg bg-primary shadow-lg p-8 pt-0 rounded">
|
||||
<div className="w-full lg:w-1/2 mr-44">
|
||||
<div className="w-1/2 fixed mr-8">
|
||||
|
||||
{gallery &&(
|
||||
<SearchInput
|
||||
placeholderTags={[
|
||||
{ value: "tags", label: "❗️ click here to add tags" },
|
||||
]}
|
||||
startingTags={gallery.tags}
|
||||
nsfwButtonEnabled={true}
|
||||
searchChanged={(search) => {}}
|
||||
nsfwChanged={(nsfw) => {}}
|
||||
tagsChanged={(tags) => { setSelectedTags(tags) }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full lg:w-1/2 pt-4">
|
||||
{gallery != null && (<>
|
||||
<select value={nsfw ? "NSFW" : "SFW"} className="mb-2 shadow-lg rounded-md bg-secondary p-2 w-full text-white" onChange={e=>{
|
||||
setNsfw(e.target.value == "NSFW");
|
||||
}}>
|
||||
<option value="" disabled >Set NSFW</option>
|
||||
<option value="NSFW" selected={nsfw}>NSFW</option>
|
||||
<option value="SFW" selected={!nsfw}>SFW</option>
|
||||
</select>
|
||||
<select onChange={e=>{
|
||||
setTier(e.target.value);
|
||||
|
||||
}} className="mb-2 shadow-lg mr-2 rounded-md bg-secondary p-2 w-full text-white">
|
||||
<option value="" disabled >Select New Tier</option>
|
||||
{tiers.map((tier, index) => (
|
||||
<option selected={tier.name==gallery.tier} key={index} value={tier.name}>{tier.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<select onChange={e=>{setThumbnail(e.target.value)}} className="mb-2 shadow-lg mr-2 rounded-md bg-secondary p-2 w-full text-white">
|
||||
<option value="" disabled selected>Select New Thumbnail</option>
|
||||
{fileNames.map((name, index) => (
|
||||
<option selected={name==gallery.thumbnail_file} key={index} value={name}>{name}</option>
|
||||
))}
|
||||
</select>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{(open) && (
|
||||
<>
|
||||
{/*
|
||||
This is the modal for holding the gallery
|
||||
*/}
|
||||
<div
|
||||
className={`fixed inset-0 transition-opacity z-30 animate-in`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
className="absolute w-full h-full inset-0 bg-secondary-dark opacity-70 z-30"
|
||||
onClick={() => setOpen(true)}
|
||||
></div>
|
||||
<div className="absolute inset-0 overflow-y-auto overflow-x-hidden no-scrollbar pt-2 w-full p-20 h-full z-30">
|
||||
<Gallery
|
||||
id={gallery.name}
|
||||
columns={3}
|
||||
closeMenu={() => setOpen(false)}
|
||||
></Gallery>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default PageComponent;
|
@ -1,102 +1,58 @@
|
||||
"use client";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
import { redirect } from "next/navigation";
|
||||
import { Vortex } from "@/components/ui/vortex";
|
||||
import GalleryThumbnail from "@/components/ui/gallery_thumbnail";
|
||||
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { User } from "@supabase/supabase-js";
|
||||
import Gallery from "@/components/ui/gallery";
|
||||
import Search from "@/components/neroshitron/search";
|
||||
import Gallery from "@/components/neroshitron/gallery";
|
||||
import Link from "next/link";
|
||||
|
||||
function PageComponent() {
|
||||
|
||||
const [selectedGallery, setSelectedGallery] = useState<string | null>(null);
|
||||
const supabase = createClient();
|
||||
|
||||
const [randomIds, setRandomIds] = useState<string[]>([]); // replace any with your gallery type
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [galleries, setGalleries] = useState<any[]>([]); // replace any with your gallery type
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [selectedGallery, setSelectedGallery] = useState<string | null>(null);
|
||||
const generateRandomString = function (length:number) {
|
||||
let result = '';
|
||||
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let charactersLength = characters.length;
|
||||
for ( let i = 0; i < length; i++ ) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const selectGallery = (gallery:string) => {
|
||||
setRandomIds([generateRandomString(3), generateRandomString(3), generateRandomString(3), generateRandomString(3)]);
|
||||
setSelectedGallery(gallery);
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const getData = async () => {
|
||||
const galleriesResponse = await fetch('/api/galleries');
|
||||
const galleriesData = await galleriesResponse.json();
|
||||
let { data: { user } } = await supabase.auth.getUser();
|
||||
setGalleries(galleriesData);
|
||||
setUser(user);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, [selectedGallery]);
|
||||
|
||||
}, []);
|
||||
return ( ( user ? (
|
||||
<div className="w-full h-full flex justify-center">
|
||||
<div className="flex-1 w-full h-full flex flex-col gap-20">
|
||||
{isOpen ? (
|
||||
<>
|
||||
<div key={randomIds[0]} className="absolute w-full h-full overflow-hidden z-2 animate-flip-up animate-ease-out animate-reverse">
|
||||
<img src="gallery_girl.png" className="float-right object-cover h-screen w-3/6" alt="Background" />
|
||||
</div>
|
||||
<div key={randomIds[1]} className="absolute items-center w-3/5 h-full ml-10 z-2 overflow-hidden animate-fade animate-ease-out animate-reverse">
|
||||
<div className="grid grid-cols-3 gap-x-10 h-full overflow-y-auto no-scrollbar pt-36">
|
||||
{galleries.map((gallery, index) => (
|
||||
<GalleryThumbnail key={index} id={gallery.id} onSelect={() => selectGallery(gallery.id)}></GalleryThumbnail>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</> ) : (
|
||||
<>
|
||||
const closeGallery = () => {
|
||||
setSelectedGallery(null);
|
||||
}
|
||||
|
||||
<div key={randomIds[2]} className="absolute w-full h-full overflow-hidden z-2 animate-flip-up animate-ease-out">
|
||||
<img src="gallery_girl.png" className="float-right object-cover h-screen w-3/6" alt="Background" />
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="w-2/4">
|
||||
<Search gallerySelected={(gallery:string)=>{setSelectedGallery(gallery)}}/>
|
||||
</div>
|
||||
<div key={randomIds[3]} className="absolute items-center w-3/5 h-full ml-10 z-2 overflow-hidden nimate-fade animate-ease-out">
|
||||
<div className="grid grid-cols-3 gap-x-10 h-full overflow-y-auto no-scrollbar pt-36">
|
||||
{galleries.map((gallery, index) => (
|
||||
<GalleryThumbnail key={index} id={gallery.id} onSelect={selectGallery}></GalleryThumbnail>
|
||||
))}
|
||||
|
||||
{selectedGallery!=null ? (
|
||||
<>
|
||||
{/*
|
||||
This is the modal for holding the gallery
|
||||
*/}
|
||||
<div
|
||||
className={`fixed inset-0 transition-opacity z-30 animate-in`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0 bg-secondary-dark opacity-70 z-30"
|
||||
onClick={() => closeGallery()}
|
||||
></div>
|
||||
<div className="absolute inset-0 overflow-y-auto overflow-x-hidden no-scrollbar pt-2 w-full p-20 z-30">
|
||||
<Gallery
|
||||
id={selectedGallery as string}
|
||||
columns={3}
|
||||
closeMenu={() => closeGallery()}
|
||||
></Gallery>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
{(isOpen ? (
|
||||
<>
|
||||
|
||||
|
||||
<div className={`fixed inset-0 transition-opacity${isOpen ? 'animate-in' : 'fade-out'}`} aria-hidden="true">
|
||||
<div className="absolute inset-0 bg-neroshi-blue-900 opacity-70" onClick={()=>setIsOpen(false)} >
|
||||
</div>
|
||||
<div className="absolute inset-0 overflow-y-auto overflow-x-hidden no-scrollbar pt-20 w-full p-20">
|
||||
<Gallery id={selectedGallery as string} closeMenu={() => setIsOpen(false)}></Gallery>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</>
|
||||
): null)}
|
||||
</div>
|
||||
|
||||
) : (
|
||||
<h1>loading</h1>
|
||||
)));
|
||||
);
|
||||
}
|
||||
|
||||
export default PageComponent;
|
@ -3,27 +3,20 @@
|
||||
@tailwind utilities;
|
||||
|
||||
|
||||
@keyframes expandFromLeft {
|
||||
0% {
|
||||
width: 0;
|
||||
}
|
||||
100% {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.my-masonry-grid {
|
||||
display: -webkit-box; /* Not needed if autoprefixing */
|
||||
display: -ms-flexbox; /* Not needed if autoprefixing */
|
||||
display: flex;
|
||||
margin-left: -30px; /* gutter size offset */
|
||||
width: auto;
|
||||
}
|
||||
.my-masonry-grid_column {
|
||||
padding-left: 30px; /* gutter size */
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
/* Style your items */
|
||||
.my-masonry-grid_column > div { /* change div to reference your elements you put in <Masonry> */
|
||||
background: grey;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
@ -54,10 +47,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-foreground/20;
|
||||
}
|
||||
.my-masonry-grid {
|
||||
display: -webkit-box; /* Not needed if autoprefixing */
|
||||
display: -ms-flexbox; /* Not needed if autoprefixing */
|
||||
display: flex;
|
||||
margin-left: -30px; /* gutter size offset */
|
||||
width: auto;
|
||||
}
|
||||
.my-masonry-grid_column {
|
||||
padding-left: 30px; /* gutter size */
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
/* Style your items */
|
||||
.my-masonry-grid_column > div { /* change div to reference your elements you put in <Masonry> */
|
||||
background: grey;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.animate-in {
|
||||
@ -74,3 +79,33 @@
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
button:disabled {
|
||||
filter: grayscale(50%);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
:root {
|
||||
--color-primary: #201240;
|
||||
--color-primary-light: #403260;
|
||||
--color-primary-dark: #100120;
|
||||
--color-secondary: #4F3D70;
|
||||
--color-secondary-light: #6F5D90;
|
||||
--color-secondary-dark: #2F1D50;
|
||||
--color-error: #862117;
|
||||
--color-error-light: #C44C4C;
|
||||
--color-error-dark: #5C0D0D;
|
||||
--color-success: #00C9A6;
|
||||
--color-success-light: #20E9C6;
|
||||
--color-success-dark: #00A986;
|
||||
--color-warning: #E17558;
|
||||
--color-warning-light: #E39578;
|
||||
--color-warning-dark: #C15538;
|
||||
--color-info: #222140;
|
||||
--color-info-light: #424260;
|
||||
--color-info-dark: #020120;
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
import { GeistSans } from "geist/font/sans";
|
||||
import "./globals.css";
|
||||
import NavigationBar from "@/components/NavigationBar";
|
||||
import { Vortex } from "@/components/ui/vortex";
|
||||
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
import NavigationBar from "@/components/neroshitron/navigation_bar";
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next"
|
||||
import { Analytics } from "@vercel/analytics/react"
|
||||
import RightHandLayoutImage from "@/components/neroshitron/right_hand_layout_image";
|
||||
import Theme from "@/components/theme";
|
||||
const defaultUrl = process.env.VERCEL_URL
|
||||
? `https://${process.env.VERCEL_URL}`
|
||||
: "http://localhost:3000";
|
||||
@ -13,6 +16,10 @@ export const metadata = {
|
||||
description: "The fastest way to build apps with Next.js and Supabase",
|
||||
};
|
||||
|
||||
const supabase = createClient();
|
||||
|
||||
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
@ -21,10 +28,14 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="en" className={GeistSans.className}>
|
||||
<body className="bg-background text-foreground">
|
||||
<div className="w-full absolute z-20">
|
||||
<Theme/>
|
||||
<RightHandLayoutImage/>
|
||||
<div className="w-full fixed z-30 text-white white">
|
||||
<NavigationBar/>
|
||||
<SpeedInsights/>
|
||||
<Analytics/>
|
||||
</div>
|
||||
<main className="min-h-screen flex flex-col items-center bg-gradient-to-r from-neroshi-blue-900 to-neroshi-blue-950">
|
||||
<main className="min-h-screen flex flex-col items-center bg-gradient-to-r from-primary to-secondary overflow-hidden">
|
||||
{children}
|
||||
</main>
|
||||
</body>
|
||||
|
@ -4,11 +4,20 @@ import { createClient } from "@/utils/supabase/server";
|
||||
import { redirect } from "next/navigation";
|
||||
import { SubmitButton } from "./submit-button";
|
||||
|
||||
export default function Login({
|
||||
export default async function Login({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: { message: string };
|
||||
}) {
|
||||
const supabase = createClient();
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
if (user) {
|
||||
return redirect("/gallery");
|
||||
}
|
||||
const signIn = async (formData: FormData) => {
|
||||
"use server";
|
||||
|
||||
@ -16,7 +25,7 @@ export default function Login({
|
||||
const password = formData.get("password") as string;
|
||||
const supabase = createClient();
|
||||
|
||||
const { error } = await supabase.auth.signInWithPassword({
|
||||
const { data:data, error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
@ -55,11 +64,11 @@ export default function Login({
|
||||
|
||||
<div className="flex-1 w-full flex flex-col gap-20 items-center animate-in"> <div className="flex-1 flex flex-col w-full px-8 sm:max-w-md justify-center gap-2">
|
||||
|
||||
<form className="animate-in flex-1 flex flex-col w-full my-32 gap-2 text-foreground">
|
||||
<form className="animate-in flex-1 flex flex-col w-full my-32 gap-2 text-white">
|
||||
|
||||
<Link
|
||||
href="/"
|
||||
className="absolute left-1 top-44 py-2 px-4 rounded-md no-underline text-foreground flex items-center group text-sm"
|
||||
className="absolute left-1 top-44 py-2 px-4 rounded-md no-underline text-white flex items-center group text-sm"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -78,36 +87,36 @@ export default function Login({
|
||||
Back
|
||||
</Link>
|
||||
<input
|
||||
className="rounded-md px-4 py-2 bg-inherit border mb-2 mx-1"
|
||||
className="rounded-md px-4 py-2 bg-inherit border mb-2 mx-1 w-full sm:w-auto"
|
||||
name="email"
|
||||
placeholder="Email Address"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
className="rounded-md px-4 py-2 bg-inherit border mb-2 mx-1"
|
||||
className="rounded-md px-4 py-2 bg-inherit border mb-2 mx-1 w-full sm:w-auto"
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password "
|
||||
required
|
||||
/>
|
||||
<div className="flex">
|
||||
<div className="flex text-white white">
|
||||
<SubmitButton
|
||||
formAction={signIn}
|
||||
className="bg-neroshi-blue-500 hover:bg-neroshi-blue-400 rounded-md px-4 py-2 text-foreground mb-2 mx-1 w-1/2"
|
||||
className="bg-success hover:bg-success-light rounded-md px-4 py-2 text-white mb-2 mx-1 w-1/2"
|
||||
pendingText="Signing In..."
|
||||
>
|
||||
Sign In
|
||||
</SubmitButton>
|
||||
<SubmitButton
|
||||
formAction={signUp}
|
||||
className="bg-neroshi-blue-300 hover:bg-neroshi-blue-200 border border-foreground/20 rounded-md px-4 py-2 text-foreground mb-2 mx-1 w-1/2"
|
||||
className="bg-info hover:bg-info-light border border-foreground/20 rounded-md px-4 py-2 text-white mb-2 mx-1 w-1/2"
|
||||
pendingText="Signing Up..."
|
||||
>
|
||||
Sign Up
|
||||
</SubmitButton>
|
||||
</div>
|
||||
{searchParams?.message && (
|
||||
<p className="mt-4 bg-foreground/10 mt-14 p-2 text-foreground text-center">
|
||||
<p className="mt-4 bg-foreground/10 mt-14 p-2 text-white text-center">
|
||||
{searchParams.message}
|
||||
</p>
|
||||
)}
|
||||
|
12
app/middleware/path-header.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
export function middleware(req:NextRequest, res:NextResponse) {
|
||||
const requestHeaders = new Headers(req.headers)
|
||||
requestHeaders.set('x-path', req.nextUrl.pathname)
|
||||
|
||||
return NextResponse.next({
|
||||
request: {
|
||||
headers: requestHeaders
|
||||
}
|
||||
})
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import crypto from 'crypto';
|
||||
|
||||
|
||||
export default async function AuthButton() {
|
||||
const supabase = createClient();
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
const signOut = async () => {
|
||||
"use server";
|
||||
|
||||
const supabase = createClient();
|
||||
await supabase.auth.signOut();
|
||||
return redirect("/login");
|
||||
};
|
||||
|
||||
|
||||
|
||||
if(user){
|
||||
let email = user.email;
|
||||
if(email != null){
|
||||
const emailHash = crypto.createHash('md5').update(email.trim().toLowerCase()).digest('hex');
|
||||
const gravatarUrl = `https://www.gravatar.com/avatar/${emailHash}`;
|
||||
return(
|
||||
<div className="flex justify-center items-center pt-2 ">
|
||||
<nav className="w-1/3 bg-neroshi-blue-300 bg-opacity-10 flex justify-center h-16 animate-in rounded-3xl" style={{ backdropFilter: 'blur(10px)' }}>
|
||||
<div className="w-full max-w-2xl flex justify-between items-center p-3 text-sm">
|
||||
<div className="flex items-center gap-2 ">
|
||||
<Link
|
||||
href="/gallery"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Gallery
|
||||
</Link>
|
||||
<Link
|
||||
href="/livestream"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Stream
|
||||
</Link>
|
||||
<Link
|
||||
href="/commissions"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Commissions
|
||||
</Link>
|
||||
<Link
|
||||
href="/subscriptions"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Subscription
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<form action={signOut}>
|
||||
<button className="py-2 px-4 rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800">
|
||||
Logout
|
||||
</button>
|
||||
</form>
|
||||
<img src={gravatarUrl} alt="Profile" className="w-10 h-10 object-cover rounded-full cursor-pointer" />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>)
|
||||
}
|
||||
}
|
||||
else{
|
||||
return( <div className="flex justify-center items-center pt-2 ">
|
||||
<nav className="w-1/3 bg-neroshi-blue-300 bg-opacity-10 flex justify-center h-16 animate-in rounded-3xl" style={{ backdropFilter: 'blur(10px)' }}>
|
||||
|
||||
<div className="w-full max-w-2xl flex justify-between items-center p-3 text-sm">
|
||||
<div className="flex items-center gap-2 ">
|
||||
<Link
|
||||
href="/gallery"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Gallery
|
||||
</Link>
|
||||
<Link
|
||||
href="/livestream"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Stream
|
||||
</Link>
|
||||
<Link
|
||||
href="/commissions"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Commissions
|
||||
</Link>
|
||||
<Link
|
||||
href="/subscriptions"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Subscription
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Link
|
||||
href="/login"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
<Link
|
||||
href="/login"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Signup
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>)
|
||||
}
|
||||
}
|
81
components/neroshitron/galleries.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
"use client;"
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import GalleryThumbnail from './gallery_thumbnail';
|
||||
|
||||
interface TagProps {
|
||||
nsfw: boolean;
|
||||
tags: string[];
|
||||
search: string;
|
||||
gallerySelected: (gallery: string) => void;
|
||||
}
|
||||
|
||||
const Galleries = ({ nsfw, tags, search, gallerySelected }: TagProps) => {
|
||||
|
||||
const [galleries, setGalleries] = useState([]);
|
||||
const [nsfwState, setNsfwState] = useState<boolean>(nsfw);
|
||||
const [tagsState, setTagsState] = useState<string[]>(tags);
|
||||
const [searchState, setSearchState] = useState<string>(search);
|
||||
|
||||
const [selectedGallery, setSelectedGallery] = useState<string | null>(null);
|
||||
const [tiers, setTiers] = useState<any[]>([]);
|
||||
|
||||
const selectGallery = (gallery: string) => {
|
||||
setSelectedGallery(gallery);
|
||||
gallerySelected(gallery);
|
||||
};
|
||||
|
||||
|
||||
const getData = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/tiers');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setTiers(data);
|
||||
} else {
|
||||
console.error('failed to fetch tiers');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching users:', error);
|
||||
}
|
||||
const galleriesResponse = await fetch(`/api/galleries?search=` + searchState + '&nsfw=' + nsfwState, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ tags: tagsState })
|
||||
});
|
||||
const galleriesData = await galleriesResponse.json();
|
||||
setGalleries(galleriesData);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, [tagsState]);
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 mx-auto ml-16 md:ml-0 pt-48 p-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-48 gap-x-4 animate-in overflow-y-scroll no-scrollbar z-0">
|
||||
|
||||
{galleries && galleries.map((gallery: any, index) => {
|
||||
const tier = tiers.find((tier) => tier.name == gallery.tier);
|
||||
const subscriptionColor = tier ? tier.color : null;
|
||||
|
||||
return (
|
||||
<GalleryThumbnail
|
||||
key={gallery.name + " " + nsfw}
|
||||
id={gallery.name}
|
||||
title={gallery.name}
|
||||
tags={gallery.tags}
|
||||
columns={gallery.columns}
|
||||
showNsfw={nsfw}
|
||||
subscription={gallery.tier as string}
|
||||
subscriptionColor={subscriptionColor}
|
||||
onSelect={selectGallery}
|
||||
nsfw={gallery.nsfw}
|
||||
></GalleryThumbnail>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Galleries;
|
222
components/neroshitron/gallery.tsx
Normal file
@ -0,0 +1,222 @@
|
||||
import { use, useState, useRef } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import Masonry from 'react-masonry-css';
|
||||
import PanZoom, { PanZoomRef } from 'react-easy-panzoom';
|
||||
|
||||
interface GalleryProps {
|
||||
id: string;
|
||||
columns: number;
|
||||
closeMenu: () => void;
|
||||
}
|
||||
|
||||
const Gallery = ({ id, columns, closeMenu }: GalleryProps) => {
|
||||
|
||||
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
||||
const [images, setImages] = useState<string[]>([]);
|
||||
const [galleryId, setGalleryId] = useState(id as string);
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const panZoomRef = useRef<any>(null);
|
||||
|
||||
const next = () => {
|
||||
if (currentIndex < images.length - 1) {
|
||||
setCurrentIndex(currentIndex + 1);
|
||||
} else {
|
||||
setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
const previous = () => {
|
||||
if (currentIndex > 0) {
|
||||
setCurrentIndex(currentIndex - 1);
|
||||
} else {
|
||||
setCurrentIndex(images.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownload = (image: string) => {
|
||||
const link = document.createElement('a');
|
||||
link.href = image;
|
||||
link.download = 'image.jpg'; // or any other filename
|
||||
link.style.display = 'none';
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
const getData = async () => {
|
||||
const thumbnailResponse = await fetch('/api/galleries/' + String(galleryId) + '/images');
|
||||
const thumbnailUrl = await thumbnailResponse.json() as string[];
|
||||
setImages(thumbnailUrl);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
switch (event.key) {
|
||||
case 'ArrowLeft':
|
||||
case 'a':
|
||||
case 'A':
|
||||
previous();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
case 'd':
|
||||
case 'D':
|
||||
next();
|
||||
break;
|
||||
case 'Escape':
|
||||
close();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
setSelectedImage(images[currentIndex]);
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
// Clean up the event listener when the component is unmounted
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
|
||||
}, [selectedImage, currentIndex]);
|
||||
|
||||
const handleClick = (image: string) => {
|
||||
setSelectedImage(image);
|
||||
setCurrentIndex(images.indexOf(image));
|
||||
};
|
||||
|
||||
const resetPanZoom = () => {
|
||||
panZoomRef.current.autoCenter();
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
if (selectedImage != null) {
|
||||
setSelectedImage(null);
|
||||
setImages([]);
|
||||
}
|
||||
else {
|
||||
closeMenu();
|
||||
}
|
||||
};
|
||||
const back_page = () => {
|
||||
if (selectedImage != null) {
|
||||
setSelectedImage(null);
|
||||
setImages([]);
|
||||
}
|
||||
};
|
||||
|
||||
const renderButtons = () => {
|
||||
return (
|
||||
<div className="z-20 bottom-10 fixed text-white pt-4 bg-primary bg-opacity-90 animate-in rounded-md" style={{ backdropFilter: 'blur(10px)' }}>
|
||||
<div className='grid grid-cols-5 pl-4 gap-4 pr-4'>
|
||||
<button
|
||||
className={`justify-center text-center w-full animate-in animate-once animate-duration-1000 animate-ease-out animate-reverse mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50 bg-error hover:bg-error-light`}
|
||||
onClick={() => close()}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
className={`justify-center text-center w-full animate-in animate-once animate-duration-1000 animate-ease-out animate-reverse mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50 ${!selectedImage ? 'opacity-50 cursor-not-allowed bg-gray-800' : 'bg-secondary hover:bg-secondary-light'}`}
|
||||
onClick={() => resetPanZoom()}
|
||||
disabled={!selectedImage}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 9V4.5M9 9H4.5M9 9 3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5 5.25 5.25" />
|
||||
</svg>
|
||||
|
||||
</button>
|
||||
<button
|
||||
className={`justify-center text-center w-full animate-in animate-once animate-duration-1000 animate-ease-out animate-reverse mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50 ${!selectedImage ? 'opacity-50 cursor-not-allowed bg-gray-800' : 'bg-secondary hover:bg-secondary-light'}`}
|
||||
onClick={() => previous()}
|
||||
disabled={!selectedImage}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5" />
|
||||
</svg>
|
||||
|
||||
</button>
|
||||
<button
|
||||
className={`justify-center text-center w-full animate-in animate-once animate-duration-1000 animate-ease-out animate-reverse mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50 ${!selectedImage ? 'opacity-50 cursor-not-allowed bg-gray-800' : 'bg-secondary hover:bg-secondary-light'}`}
|
||||
onClick={() => next()}
|
||||
disabled={!selectedImage}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
className={`justify-center text-center w-full animate-in animate-once animate-duration-1000 animate-ease-out animate-reverse mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50 ${!selectedImage ? 'opacity-50 cursor-not-allowed bg-gray-800' : 'bg-success hover:bg-success-light'}`}
|
||||
onClick={() => selectedImage && handleDownload(selectedImage)}
|
||||
disabled={!selectedImage}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="m9 13.5 3 3m0 0 3-3m-3 3v-6m1.06-4.19-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div >
|
||||
<div className="z-20" style={{ width: selectedImage ? "100%" : "auto", height: selectedImage ? "100%" : "auto" }}>
|
||||
<div className='flex justify-center items-center pt-2 pb-20'>
|
||||
{renderButtons()}
|
||||
</div>
|
||||
{selectedImage ? (
|
||||
<>
|
||||
<PanZoom
|
||||
key={selectedImage}
|
||||
autoCenter={true}
|
||||
ref={panZoomRef}
|
||||
>
|
||||
<div id="image-container" >
|
||||
<img
|
||||
src={images[currentIndex]}
|
||||
style={{ objectFit: "contain", maxWidth: "100%", maxHeight: "calc(100vh - 20px)", pointerEvents: "none" }}
|
||||
className="cursor-pointer animate-in w-full h-auto"
|
||||
>
|
||||
</img>
|
||||
</div>
|
||||
</PanZoom>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className="z-30"
|
||||
style={{
|
||||
display: selectedImage ? "flex" : "block",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
> <div className='flex justify-center items-center pt-2 '>
|
||||
|
||||
<Masonry
|
||||
breakpointCols={3}
|
||||
className="my-masonry-grid pl-6 "
|
||||
style={{ width: selectedImage ? "50%" : "100%" }}
|
||||
>
|
||||
{images
|
||||
.filter((img) => img !== selectedImage)
|
||||
.map((image, index) => (
|
||||
<img
|
||||
key={index}
|
||||
src={image}
|
||||
onClick={() => handleClick(image)}
|
||||
className={`animate-in animate-once animate-duration-1000 animate-ease-out animate-reverse hover:scale-105 p-2 cursor-pointer my-2 transition-all opacity-100 duration-500 ease-in-out transform`}
|
||||
/>
|
||||
))}
|
||||
</Masonry>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Gallery;
|
106
components/neroshitron/gallery_thumbnail.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
interface GalleryThumbnailProps {
|
||||
id: string;
|
||||
columns: number;
|
||||
onSelect: (id: string, columns: number) => void;
|
||||
title: string;
|
||||
subscription: string;
|
||||
subscriptionColor: string;
|
||||
tags: string[];
|
||||
showNsfw: boolean;
|
||||
nsfw: boolean;
|
||||
}
|
||||
|
||||
const GalleryThumbnail = ({ id, columns, onSelect, title, showNsfw, nsfw, subscription, subscriptionColor, tags }: GalleryThumbnailProps) => {
|
||||
const [galleryId, setGalleryId] = useState<string>(id);
|
||||
const [thumbnailUrl, setThumbnailUrl] = useState<string>('');
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [galleryCollumns, setColumns] = useState<number>(columns);
|
||||
const [imageCount, setImageCount] = useState<number>(0);
|
||||
const [nsfwState, setNsfw] = useState<boolean>(nsfw);
|
||||
const [showNsfwState, setShowNsfw] = useState<boolean>(showNsfw);
|
||||
const [subscriptionState, setSubscription] = useState<string>(subscription);
|
||||
const [tagsState, setTags] = useState<string[]>(tags);
|
||||
console.log(subscriptionColor)
|
||||
const openGallery = () => {
|
||||
onSelect(galleryId, galleryCollumns);
|
||||
};
|
||||
|
||||
const getData = async () => {
|
||||
setIsLoading(true);
|
||||
const thumbnailResponse = await fetch('/api/galleries/' + id + '/thumbnail?nsfw=' + showNsfwState);
|
||||
const thumbnailUrl = await thumbnailResponse.text();
|
||||
const imagesCountResponse = await fetch('/api/galleries/' + id + '/images/count');
|
||||
const imageCount = await imagesCountResponse.json() as number;
|
||||
setImageCount(imageCount);
|
||||
setThumbnailUrl(thumbnailUrl);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, [galleryId]);
|
||||
|
||||
return (
|
||||
<div className=" py-3 sm:max-w-xl sm:mx-auto flex-3 animate-fade-up animate-once animate-duration-1000 animate-ease-out animate-normal animate-fill-forwards">
|
||||
<div className="h-48 overflow-visible w-full relative hover:scale-95 rounded-3xl" style={{ cursor: 'pointer'}}>
|
||||
{!isLoading ? (
|
||||
<>
|
||||
<img
|
||||
className={`aspect-content rounded-3xl shadow-lg`}
|
||||
src={thumbnailUrl}
|
||||
alt=""
|
||||
onClick={openGallery}
|
||||
key={galleryId}
|
||||
style={{ width: '20rem', height: '20rem', objectFit: 'cover' }}
|
||||
/>
|
||||
<div className="bottom-0 left-0 w-full h-10% p-2 rounded-md flex flex-col justify-end">
|
||||
<div className="text-white flex justify-between">
|
||||
<div>
|
||||
<div className="flex">
|
||||
<h3 className=" pr-4 text-lg font-bold break-words" style={{ lineHeight: '2rem', textShadow: '0 0 2px black' }}>{title}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-white flex justify-between">
|
||||
<div className="flex items-center">
|
||||
<span className="bg-secondary text-white mr-2 px-2 py-1 rounded-md text-sm flex items-center h-full">
|
||||
<span className="text-center">{imageCount}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="pl-2 size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
|
||||
</svg>
|
||||
</span>
|
||||
{nsfwState && (
|
||||
<span className=" bg-error text-white px-2 py-1 mr-2 rounded-md text-sm h-full flex items-center">NSFW</span>
|
||||
)}
|
||||
<span className="text-white px-2 py-1 rounded-md text-sm h-full flex items-center" style={{ cursor: 'pointer', backgroundColor: subscriptionColor }}>Free</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="text-white flex justify-between">
|
||||
<div>
|
||||
<div className="flex">
|
||||
{tagsState.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className={`pr-4 text-sm font-bold break-words"
|
||||
style={{ lineHeight: '2rem', textShadow: '0 0 2px black' }`}
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="animate-pulse bg-secondary-light rounded-3xl" style={{ width: '20rem', height: '20rem' }}></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GalleryThumbnail;
|
149
components/neroshitron/navigation_bar.tsx
Normal file
@ -0,0 +1,149 @@
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import Link from "next/link";
|
||||
import { redirect, useRouter } from "next/navigation";
|
||||
import crypto from 'crypto';
|
||||
import { headers } from "next/headers";
|
||||
|
||||
|
||||
export default async function AuthButton() {
|
||||
const supabase = createClient();
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
const signOut = async () => {
|
||||
"use server";
|
||||
|
||||
const supabase = createClient();
|
||||
await supabase.auth.signOut();
|
||||
return redirect("/login");
|
||||
};
|
||||
|
||||
const heads = headers()
|
||||
const currentPage = heads.get('x-path')
|
||||
const getGravatarUrl = () => {
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
let email = user.email;
|
||||
if (email != null) {
|
||||
const emailHash = crypto.createHash('md5').update(email.trim().toLowerCase()).digest('hex');
|
||||
return `https://www.gravatar.com/avatar/${emailHash}`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
const url = getGravatarUrl();
|
||||
|
||||
const admins = await supabase.from('admins').select('user_id');
|
||||
let isAdmin = false;
|
||||
if(!admins.error) {
|
||||
for (const admin of admins.data) {
|
||||
if (admin.user_id == user?.id) {
|
||||
isAdmin = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center pt-2 ">
|
||||
<nav className="w-auto bg-primary bg-opacity-40 flex justify-center z-10 h-16 animate-in rounded-md shadow-lg" style={{ backdropFilter: 'blur(10px)' }}>
|
||||
<div className="w-auto flex justify-between items-center p-3 text-sm">
|
||||
<div className="flex items-center gap-2 z-10">
|
||||
|
||||
{/* This is admin stuff */}
|
||||
|
||||
{(isAdmin) && (
|
||||
<>
|
||||
<Link
|
||||
href="/gallery/admin/"
|
||||
className={`py-2 px-3 w-38 text-center flex rounded-md no-underline bg-secondary hover:bg-secondary-light`}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="lg:hidden size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M11.42 15.17 17.25 21A2.652 2.652 0 0 0 21 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 1 1-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 0 0 4.486-6.336l-3.276 3.277a3.004 3.004 0 0 1-2.25-2.25l3.276-3.276a4.5 4.5 0 0 0-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437 1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008Z" />
|
||||
</svg>
|
||||
<span className="hidden lg:block">Gallery Admin</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/admin/"
|
||||
className={`py-2 px-3 w-38 text-center flex rounded-md no-underline bg-secondary hover:bg-secondary-light`}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="lg:hidden size-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||
</svg>
|
||||
<span className="hidden lg:block">System Settings</span>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Link
|
||||
href="/gallery"
|
||||
className={`py-2 px-3 flex rounded-md no-underline ${currentPage!="gallery" ? 'bg-primary hover:bg-primary-light' : 'bg-secondary hover:bg-secondary-light'}`}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
|
||||
</svg>
|
||||
|
||||
<span className="hidden lg:block">Gallery</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/livestream"
|
||||
className={`py-2 px-3 flex rounded-md no-underline ${currentPage!="livestream" ? 'bg-primary hover:bg-primary-light' : 'bg-secondary hover:bg-secondary-light'}`}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z" />
|
||||
</svg>
|
||||
<span className="hidden lg:block">Livestream</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/commissions"
|
||||
className={`py-2 px-3 flex rounded-md no-underline ${currentPage!="commissions" ? 'bg-primary hover:bg-primary-light' : 'bg-secondary hover:bg-secondary-light'}`}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 10.5V6a3.75 3.75 0 1 0-7.5 0v4.5m11.356-1.993 1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 0 1-1.12-1.243l1.264-12A1.125 1.125 0 0 1 5.513 7.5h12.974c.576 0 1.059.435 1.119 1.007ZM8.625 10.5a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm7.5 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
|
||||
</svg>
|
||||
<span className="hidden lg:block">Commissions</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/subscriptions"
|
||||
className={`py-2 px-3 flex rounded-md no-underline ${currentPage!="subscriptions" ? 'bg-primary hover:bg-primary-light' : 'bg-secondary hover:bg-secondary-light'}`}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
||||
</svg>
|
||||
<span className="hidden lg:block">Subscription</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{(user != null) ? (
|
||||
<>
|
||||
<form action={signOut}>
|
||||
<button className="py-2 px-4 ml-2 rounded-md no-underline bg-error hover:bg-error-light">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden ">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 10.5V6.75a4.5 4.5 0 1 1 9 0v3.75M3.75 21.75h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H3.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z" />
|
||||
</svg>
|
||||
<span className="hidden lg:block">Logout</span>
|
||||
</button>
|
||||
</form>
|
||||
<a href="https://gravatar.com/" target="_blank">
|
||||
<img src={url} alt="Profile" className="w-10 h-10 object-cover rounded-full cursor-pointer" />
|
||||
</a>
|
||||
</>
|
||||
) : (
|
||||
<Link
|
||||
href="/subscriptions"
|
||||
className={`ml-2 py-2 px-3 flex rounded-md no-underline bg-success hover:bg-success-light`}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
||||
</svg>
|
||||
<span className="hidden lg:block">Login</span>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>)
|
||||
}
|
16
components/neroshitron/right_hand_layout_image.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
"use client;"
|
||||
import React from 'react';
|
||||
|
||||
const RightHandLayoutImage: React.FC = () => {
|
||||
return (
|
||||
<div className="fixed w-full h-full overflow-hidden z-0 animate-fade-left animate-once animate-duration-[2000ms] animate-normal animate-fill-forwards">
|
||||
<img
|
||||
src="/gallery_girl.png"
|
||||
className="float-right object-cover h-screen w-full lg:w-5/6 xl:w-3/6 opacity-50 overflow-hidden"
|
||||
alt="Background"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RightHandLayoutImage;
|
60
components/neroshitron/search.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
"use client;"
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import SearchInput from '@/components/neroshitron/search_input';
|
||||
import Galleries from './galleries';
|
||||
|
||||
interface SearchProps {
|
||||
gallerySelected: (gallery: string) => void;
|
||||
}
|
||||
|
||||
const Search = ({ gallerySelected }: SearchProps) => {
|
||||
const [tags, setTags] = useState<string[]>([]);
|
||||
const [search, setSearch] = useState<string>('');
|
||||
const [nsfw, setNsfw] = useState<boolean>(false);
|
||||
const [gallery, setGallery] = useState<string | null>(null);
|
||||
|
||||
const getData = async () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, [search]);
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, [nsfw]);
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, [tags]);
|
||||
useEffect(() => {
|
||||
getData();
|
||||
if (gallery != null)
|
||||
gallerySelected(gallery);
|
||||
}, [gallery]);
|
||||
//TRY TESTING WITH THIS REMOVED!
|
||||
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Galleries gallerySelected={(gallery: string) => { setGallery(gallery) }} key={search + "-" + tags.length + "-" + nsfw} search={search} nsfw={nsfw} tags={tags} />
|
||||
<section className="fixed flex items-center w-full p-8 pt-20 opacity-90 animate-in animate-once animate-duration-500">
|
||||
<div className="container mx-auto py-8">
|
||||
<SearchInput
|
||||
|
||||
startingTags={[]}
|
||||
placeholderTags={[
|
||||
{ value: "neroshi", label: "🧑🎨 neroshi" },
|
||||
{ value: "neroshi", label: "❗️ click here for tags to search!" },
|
||||
]} nsfwButtonEnabled={true} searchChanged={(search) => { setSearch(search) }} nsfwChanged={(nsfw) => { setNsfw(nsfw) }} tagsChanged={(tags) => { setTags(tags); }} />
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Search;
|
190
components/neroshitron/search_input.tsx
Normal file
@ -0,0 +1,190 @@
|
||||
"use client;"
|
||||
import React, { useState, useEffect, useRef, forwardRef } from 'react';
|
||||
import TagSelector from '../neroshitron/tag_selector';
|
||||
import Select from "react-tailwindcss-select";
|
||||
import { SelectValue } from 'react-tailwindcss-select/dist/components/type';
|
||||
import { Option } from 'react-tailwindcss-select/dist/components/type';
|
||||
|
||||
interface SearchInputProps {
|
||||
tagsChanged: (tags: string[]) => void;
|
||||
searchChanged: (search: string) => void;
|
||||
nsfwChanged: (nsfw: boolean) => void;
|
||||
nsfwButtonEnabled: boolean | null;
|
||||
placeholderTags: Option[];
|
||||
startingTags: string[] | null;
|
||||
}
|
||||
|
||||
const SearchInput = ({ tagsChanged, searchChanged, nsfwChanged, nsfwButtonEnabled, placeholderTags, startingTags }: SearchInputProps) => {
|
||||
|
||||
|
||||
|
||||
|
||||
const [tagSearch, setTagSearch] = useState<string>('');
|
||||
const [nsfw, setNsfw] = useState<boolean>(false);
|
||||
const [selectedTags, setSelectedTags] = useState<string[]>(startingTags ?? []);
|
||||
const [selectedTagsInput, setSelectedTagsInput] = useState<Option[]>([...placeholderTags, ...(startingTags ?? []).map((tag) => ({ value: tag, label: tag }))]);
|
||||
const [selectingTags, setSelectingTags] = useState<boolean>(false);
|
||||
const tagSelectorRef = React.useRef(null);
|
||||
const [tags, setTags] = useState<any[]>([]);
|
||||
|
||||
const getData = async () => {
|
||||
const tagsResponse = await fetch(`/api/galleries/tags`);
|
||||
const tagsData = await tagsResponse.json();
|
||||
setTags(tagsData);
|
||||
}
|
||||
|
||||
const updateTags = (newTags: string[]) => {
|
||||
setSelectedTags(newTags)
|
||||
}
|
||||
|
||||
const onTagsClosed = (tags: string[]) => {
|
||||
setSelectingTags(false);
|
||||
}
|
||||
|
||||
const openTags = () => {
|
||||
setSelectingTags(true);
|
||||
if (selectingTags) {
|
||||
onTagsClosed(selectedTags);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
tagsChanged(selectedTags.filter(tag => tag != "neroshi"));
|
||||
}, [selectedTags]);
|
||||
useEffect(() => {
|
||||
nsfwChanged(nsfw);
|
||||
}, [nsfw]);
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
const [color, setColor] = useState('black');
|
||||
const selectRef = useRef(null);
|
||||
const [currentTag, setCurrentTag] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
|
||||
if (event.key === 'ArrowUp') {
|
||||
const currentIndex = tags.findIndex(tag => tag.name === currentTag);
|
||||
const newIndex = currentIndex === 0 ? tags.length - 1 : currentIndex - 1;
|
||||
setCurrentTag(tags[newIndex].name);
|
||||
} else if (event.key === 'ArrowDown') {
|
||||
const currentIndex = tags.findIndex(tag => tag.name === currentTag);
|
||||
const newIndex = currentIndex === tags.length - 1 ? 0 : currentIndex + 1;
|
||||
setCurrentTag(tags[newIndex].name);
|
||||
} else if (event.key === 'Enter') {
|
||||
const currentIndex = tags.findIndex(tag => tag.name === currentTag);
|
||||
if (currentIndex !== -1 && !selectedTags.includes(tags[currentIndex].name)) {
|
||||
setSelectedTags([...selectedTags, tags[currentIndex].name]);
|
||||
const tagsInput = selectedTagsInput;
|
||||
tagsInput.push({ value: tags[currentIndex].name, label: tags[currentIndex].name });
|
||||
setSelectedTagsInput(tagsInput);
|
||||
setCurrentTag('');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [currentTag, tags]);
|
||||
|
||||
|
||||
const tagOptions = tags.map((tag: { name: string; }) => ({ value: tag.name, label: tag.name }));
|
||||
return (
|
||||
<>
|
||||
<div className={` opacity 0 relative w-full flex flex-col items-center justify-center z-10`}>
|
||||
<div className="search-box mx-auto my-auto w-full">
|
||||
<div className={`flex flex-row`}>
|
||||
|
||||
{(selectingTags) ? (
|
||||
<>
|
||||
<input autoFocus value={tagSearch} onChange={(e) => setTagSearch(e.target.value)} className="rounded-l-md h-16 bg-gray-100 text-grey-darker py-2 font-normal text-grey-darkest border border-gray-100 font-bold w-full py-1 px-2 outline-none text-lg text-gray-600" type="text" placeholder="Looking for specific tag?" />
|
||||
|
||||
<span className="flex items-center bg-gray-100 rounded rounded-l-none border-0 px-3 font-bold text-grey-100">
|
||||
<button key="back" onClick={() => { openTags() }} type="button" className={`animate-in bg-pink-900 hover:bg-pink-800 text-lg text-white font-bold py-3 px-6 rounded`}>
|
||||
Back
|
||||
</button>
|
||||
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<div className="w-full top-0">
|
||||
<Select isMultiple isSearchable isClearable searchInputPlaceholder='Start typing to search tags...'
|
||||
options={tagOptions}
|
||||
placeholder="Select tags for your search"
|
||||
onChange={(value: Option | Option[] | null) => {
|
||||
if (value === null) {
|
||||
setSelectedTags([]);
|
||||
setSelectedTagsInput([]);
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
setSelectedTags(value.map((option) => option.value));
|
||||
setSelectedTagsInput(value as Option[])
|
||||
} else if (value) {
|
||||
setSelectedTags([value.value]);
|
||||
setSelectedTagsInput([value])
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
setSelectedTags(value.map((option) => option.value));
|
||||
setSelectedTagsInput(value.filter((option) => option.value !== 'placeholder'));
|
||||
} else if (value) {
|
||||
setSelectedTags([value.value]);
|
||||
setSelectedTagsInput([value]);
|
||||
}
|
||||
}}
|
||||
onSearchInputChange={(value) => {
|
||||
|
||||
}}
|
||||
classNames={{
|
||||
|
||||
menu: "bg-secondary-dark text-white pb-4 rounded",
|
||||
searchBox: "rounded-md bg-secondary w-1/2 text-white w-full mt-2 p-2 mb-2 animate-in",
|
||||
searchIcon: "hidden",
|
||||
tagItem: (value) => "hover:scale-95 bg-primary-light rounded-md pl-2 p-1 m-1 flex",
|
||||
tagItemText: "text-white animate-in",
|
||||
closeIcon: "text-white",
|
||||
tagItemIconContainer:"animate-in"
|
||||
}}
|
||||
|
||||
formatOptionLabel={data => (
|
||||
<li key={"tag-" + data.value}
|
||||
className={`animate-in block transition rounded duration-200 px-2 py-2 cursor-pointer select-none truncate pt-2 bg-primary text-white ${currentTag==data.value ? "bg-primary-light" : ""} hover:bg-primary-light
|
||||
}`}
|
||||
>
|
||||
{data.label}
|
||||
</li>
|
||||
)}
|
||||
value={selectedTagsInput}
|
||||
primaryColor={"indigo"} />
|
||||
|
||||
</div>
|
||||
{(nsfwButtonEnabled) && (
|
||||
<span className="w-1/6 border-0 font-bold text-grey-100">
|
||||
<button
|
||||
onClick={() => { setNsfw(!nsfw) }}
|
||||
type="button"
|
||||
className={`animate-in text-sm text-white font-bold h-full w-16 px-2 rounded rounded-l-none ${nsfw ? "bg-error hover:bg-error-light" : "bg-success hover:bg-success-light"}`}
|
||||
|
||||
>
|
||||
{nsfw ? "NSFW" : "SFW"}
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{(selectingTags) &&
|
||||
<TagSelector key={tagSearch} tagSearch={tagSearch} tagsChanged={(newTags: string[]) => { updateTags(newTags) }} selectedTagsInput={selectedTags} ref={tagSelectorRef} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchInput;
|
20
components/neroshitron/tag_pill.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
"use client;"
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
interface TagProps { onTagClicked: (tag: string) => void, selected: boolean, tag: string }
|
||||
|
||||
const Tag = ({ onTagClicked, selected, tag, }: TagProps) => {
|
||||
|
||||
return (
|
||||
<button
|
||||
key={tag}
|
||||
type="button"
|
||||
className={`animate-in w-full h-8 rounded-md no-underline text-sm text-white py-1 font-medium text-center ${selected ? 'hover:bg-pink-800 bg-pink-900' : 'hover:bg-pink-600 bg-neroshi-blue-800 border-neroshi-blue-900 border-2'}`}
|
||||
onClick={() => onTagClicked(tag)}
|
||||
>
|
||||
{tag}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tag;
|
61
components/neroshitron/tag_selector.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
"use client;"
|
||||
import React, { forwardRef, useState, useEffect } from 'react';
|
||||
import Tag from './tag_pill';
|
||||
|
||||
interface TagSelectorProps {
|
||||
tagSearch: string,
|
||||
selectedTagsInput: string[],
|
||||
tagsChanged: (tags: string[]) => void
|
||||
}
|
||||
|
||||
const TagSelector = forwardRef<TagSelectorProps, { tagSearch: string, selectedTagsInput: string[], tagsChanged: (tags: string[]) => void }>((props, ref) => {
|
||||
|
||||
const [tags, setTags] = useState<any[]>([]);
|
||||
const [tagSearch, setTagSearch] = useState<string>(props.tagSearch);
|
||||
const [selectedTags, setSelectedTags] = useState<string[]>(props.selectedTagsInput);
|
||||
|
||||
const handleTag = (tag: string) => {
|
||||
if (selectedTags.includes(tag)) {
|
||||
setSelectedTags(selectedTags.filter(t => t !== tag));
|
||||
} else {
|
||||
setSelectedTags([...selectedTags, tag]);
|
||||
}
|
||||
setTags(selectedTags);
|
||||
};
|
||||
|
||||
const getData = async () => {
|
||||
const tagsResponse = await fetch(`/api/galleries/tags`);
|
||||
const tagsData = await tagsResponse.json();
|
||||
setTags(tagsData);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
props.tagsChanged(selectedTags);
|
||||
getData();
|
||||
}, [selectedTags, tagSearch]);
|
||||
|
||||
useEffect(() => {
|
||||
props.tagsChanged(selectedTags);
|
||||
getData();
|
||||
}, [selectedTags, tagSearch]);
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
<div className="flex md:w-full pt-4 justify-center items-center">
|
||||
{(tags.length > 0) && (
|
||||
<div className="z-10 grid p-4 grid-cols-2 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-1 w-full max-h-96 overflow-y-scroll no-scrollbar pt-4 bg-neroshi-blue-900 rounded-md opacity-90 backdrop-filter backdrop-blur-md mx-auto">
|
||||
{tags.map((tag: any) => (
|
||||
(tagSearch === '' || tag.name.toLowerCase().includes(tagSearch.toLowerCase())) && // Updated condition
|
||||
<Tag tag={tag.name} selected={selectedTags.includes(tag.name)} onTagClicked={(tag) => handleTag(tag)} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default TagSelector;
|
37
components/theme.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
|
||||
"use client";
|
||||
import { useEffect, useState } from 'react';
|
||||
import { createClient } from '@/utils/supabase/client';
|
||||
|
||||
interface GalleryThumbnailProps {}
|
||||
|
||||
const ThemeProvider = ({}: GalleryThumbnailProps) => {
|
||||
const [data, setData] = useState<any[]>([]); // State to store the fetched data
|
||||
|
||||
const getData = async () => {
|
||||
const supabase = createClient();
|
||||
const { data, error } = await supabase.from('interface_configurations').select('*');
|
||||
if (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
} else {
|
||||
setData(data || []);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Update variables when data changes
|
||||
for (const config of data) {
|
||||
if (config.type === 'color') {
|
||||
document.documentElement.style.setProperty(`--color-${config.name}`, config.value);
|
||||
}
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default ThemeProvider;
|
@ -1,166 +0,0 @@
|
||||
import { use, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import Masonry from 'react-masonry-css';
|
||||
|
||||
interface GalleryProps {
|
||||
id: string;
|
||||
closeMenu: () => void;
|
||||
}
|
||||
|
||||
const Gallery = ({ id, closeMenu }: GalleryProps) => {
|
||||
|
||||
const [isSingle, setIsSingle] = useState<boolean>(false);
|
||||
const [loaded, setLoaded] = useState({})
|
||||
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
||||
const [images, setImages] = useState<string[]>([]);
|
||||
const [galleryId, setGalleryId] = useState(id as string);
|
||||
console.log(id)
|
||||
const getData = async () => {
|
||||
const thumbnailResponse = await fetch('/api/galleries/'+String(galleryId)+'/images');
|
||||
const thumbnailUrl = await thumbnailResponse.json() as string[];
|
||||
setImages(thumbnailUrl);
|
||||
}
|
||||
const generateRandomString = function (length:number) {
|
||||
let result = '';
|
||||
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let charactersLength = characters.length;
|
||||
for ( let i = 0; i < length; i++ ) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const renderButtons = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="z-10 fixed left-6 bottom-4 w-96 h-32 bg-purple-900 bg-opacity-10 flex justify-center h-16 animate-in rounded-3xl" style={{ backdropFilter: 'blur(10px)' }}>
|
||||
</div>
|
||||
<button
|
||||
className={`fixed left-40 bottom-5 text-center w-56 animate-in animate-once animate-duration-1000 animate-ease-out animate-reverse mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50 ${!selectedImage ? 'opacity-50 cursor-not-allowed bg-gray-800' : 'bg-neroshi-blue-800'}`}
|
||||
onClick={() => selectedImage && handleDownload(selectedImage)}
|
||||
disabled={!selectedImage}
|
||||
>
|
||||
Download Current Image
|
||||
</button>
|
||||
<button
|
||||
className={`fixed left-40 bottom-16 w-56 text-center animate-in animate-once animate-duration-1000 animate-ease-out animate-reverse mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50 ${!selectedImage ? 'opacity-50 cursor-not-allowed bg-gray-800' : 'bg-neroshi-blue-800'}`}
|
||||
onClick={() => selectedImage && open()}
|
||||
disabled={!selectedImage}
|
||||
>
|
||||
Open Image in New Tab
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const handleDownload = (image: string) => {
|
||||
const link = document.createElement('a');
|
||||
link.href = image;
|
||||
link.download = 'image.jpg'; // or any other filename
|
||||
link.style.display = 'none';
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
useEffect(() => {
|
||||
getData();
|
||||
if (images.length === 1) {
|
||||
setIsSingle(true);
|
||||
setSelectedImage(images[0]);
|
||||
}
|
||||
}, [selectedImage]);
|
||||
|
||||
const handleClick = (image: string) => {
|
||||
setSelectedImage(image);
|
||||
};
|
||||
|
||||
const open = () => {
|
||||
if(selectedImage === null) return;
|
||||
console.log(selectedImage)
|
||||
let base64Image = selectedImage.split(';base64,').pop();
|
||||
if(!base64Image) return;
|
||||
let blob = new Blob([Uint8Array.from(atob(base64Image), c => c.charCodeAt(0))], {type: 'image/jpeg'}); // adjust the type as needed
|
||||
let url = URL.createObjectURL(blob);
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
const breakpointColumnsObj = {
|
||||
default: 3
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="fixed bg-purple-800 left-10 bottom-5 animate-in animate-once animate-duration-1000 animate-ease-out animate-reverse mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50"
|
||||
onClick={()=> closeMenu()}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="mr-2 h-4 w-4 transition-transform group-hover:-translate-x-1"
|
||||
>
|
||||
<polyline points="15 18 9 12 15 6" />
|
||||
</svg>{" "}
|
||||
Back
|
||||
</button>
|
||||
|
||||
|
||||
<div className='z-10 pt-10' style={{ display: selectedImage ? 'flex' : 'block', alignItems: 'flex-start' }}>
|
||||
{isSingle ? (
|
||||
<div className='w-full h-full flex items-center'>
|
||||
<>
|
||||
{selectedImage &&
|
||||
<img
|
||||
src={selectedImage}
|
||||
style={{ objectFit: 'contain' }}
|
||||
className="cursor-pointer animate-in w-full h-auto"
|
||||
onClick={() => setSelectedImage(null)}
|
||||
/>
|
||||
}
|
||||
{renderButtons()}
|
||||
</>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className='w-full h-full flex items-center'>
|
||||
<>
|
||||
{renderButtons()}
|
||||
{selectedImage &&
|
||||
<img
|
||||
src={selectedImage}
|
||||
style={{ objectFit: 'contain' }}
|
||||
className="cursor-pointer animate-in w-full h-auto"
|
||||
onClick={() => setSelectedImage(null)}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
</div>
|
||||
<Masonry
|
||||
breakpointCols={selectedImage==null ? 4 : 2}
|
||||
className="my-masonry-grid"
|
||||
style={{ width: selectedImage ? '50%' : '100%' }}
|
||||
>
|
||||
{images.filter(img => img !== selectedImage).map((image, index) => (
|
||||
<img
|
||||
key={generateRandomString(3)}
|
||||
src={image}
|
||||
onClick={() => handleClick(image)}
|
||||
className={`animate-in animate-once animate-duration-1000 animate-ease-out animate-reverse hover:scale-105 p-2 cursor-pointer my-2 transition-all opacity-100 duration-500 ease-in-out transform`}
|
||||
/>
|
||||
))}
|
||||
</Masonry>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Gallery;
|
@ -1,44 +0,0 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
interface GalleryThumbnailProps {
|
||||
id: string;
|
||||
onSelect: (id:string) => void;
|
||||
}
|
||||
|
||||
const GalleryThumbnail = ({ id, onSelect }: GalleryThumbnailProps) => {
|
||||
const [galleryId, setGalleryId] = useState<string>(id);
|
||||
const [thumbnailUrl, setThumbnailUrl] = useState<string>('');
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
|
||||
const openGallery = () => {
|
||||
onSelect(galleryId);
|
||||
};
|
||||
|
||||
const getData = async () => {
|
||||
setIsLoading(true);
|
||||
const thumbnailResponse = await fetch('/api/galleries/'+galleryId+'/thumbnail');
|
||||
const thumbnailUrl = await thumbnailResponse.text();
|
||||
setThumbnailUrl(thumbnailUrl);
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="py-3 sm:max-w-xl sm:mx-auto flex-3">
|
||||
<div className="h-48 overflow-visible w-full relative hover:scale-105 shadow-lg bg-gray-400 rounded-3xl">
|
||||
{!isLoading && <img
|
||||
className={`aspect-content rounded-3xl`}
|
||||
src={thumbnailUrl}
|
||||
alt=""
|
||||
onClick={openGallery}
|
||||
style={{ width: '20rem', height: '20rem', objectFit: 'cover' }}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GalleryThumbnail;
|
@ -5,8 +5,6 @@ x-logging: &x-logging
|
||||
options:
|
||||
max-file: '5'
|
||||
max-size: '10m'
|
||||
include:
|
||||
- "supabase/docker-compose.yml"
|
||||
services:
|
||||
# neroshitron:
|
||||
# build:
|
||||
@ -24,14 +22,5 @@ services:
|
||||
volumes:
|
||||
- ./data:/owncast/data
|
||||
|
||||
maildev:
|
||||
image: maildev/maildev:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 1080:1080
|
||||
- 1025:1025
|
||||
volumes:
|
||||
- ./data:/maildev/data
|
||||
|
||||
volumes:
|
||||
db-config:
|
||||
|
503
docs/.$diagrams.drawio.bkp
Normal file
@ -0,0 +1,503 @@
|
||||
<mxfile host="Electron" modified="2024-06-04T04:27:05.636Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/24.2.5 Chrome/120.0.6099.109 Electron/28.1.0 Safari/537.36" etag="x7V6P6bjO-UHE86F-Cl3" version="24.2.5" type="device">
|
||||
<diagram name="Page-1" id="F3YAVjulPUqdYhbqfjdd">
|
||||
<mxGraphModel dx="1877" dy="2420" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-28" value="<h1>Database Design</h1>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="332.5" y="-70" width="390" height="10" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-29" value="<h1>UX Flow</h1>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="30" y="620" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-37" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-33" target="9zziB1Dtd-V9IfowJO3V-36" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-33" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="35" y="690" width="30" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-39" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-36" target="9zziB1Dtd-V9IfowJO3V-38" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-36" value="Open Site" style="whiteSpace=wrap;html=1;verticalAlign=top;" parent="1" vertex="1">
|
||||
<mxGeometry x="105" y="705" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-42" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-38" target="9zziB1Dtd-V9IfowJO3V-41" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-43" value="No" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-42" vertex="1" connectable="0">
|
||||
<mxGeometry x="0.2533" y="2" relative="1" as="geometry">
|
||||
<mxPoint x="2" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-48" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-38" target="9zziB1Dtd-V9IfowJO3V-47" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-72" value="Yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-48" vertex="1" connectable="0">
|
||||
<mxGeometry x="-0.2273" relative="1" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-73" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-38" target="9zziB1Dtd-V9IfowJO3V-71" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="265" y="740" />
|
||||
<mxPoint x="265" y="795" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-74" value="Yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-73" vertex="1" connectable="0">
|
||||
<mxGeometry x="0.0411" y="1" relative="1" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-82" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-38" target="9zziB1Dtd-V9IfowJO3V-81" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="305" y="890" />
|
||||
<mxPoint x="93" y="890" />
|
||||
<mxPoint x="93" y="940" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-83" value="Yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-82" vertex="1" connectable="0">
|
||||
<mxGeometry x="-0.8649" y="-1" relative="1" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-92" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-38" target="9zziB1Dtd-V9IfowJO3V-91" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-93" value="Yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-92" vertex="1" connectable="0">
|
||||
<mxGeometry x="-0.6335" y="-2" relative="1" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-38" value="<br>Logged<br>In" style="rhombus;whiteSpace=wrap;html=1;verticalAlign=top;" parent="1" vertex="1">
|
||||
<mxGeometry x="265" y="680" width="80" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-46" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-41" target="9zziB1Dtd-V9IfowJO3V-45" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-41" value="Login/Signup Page" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#a20025;strokeColor=#6F0000;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="245" y="620" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-49" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;dashed=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-45" target="9zziB1Dtd-V9IfowJO3V-47" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-50" value="Click Activation<br>Email Link" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-49" vertex="1" connectable="0">
|
||||
<mxGeometry x="0.1683" y="1" relative="1" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-45" value="Send Confirmation Email" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#a20025;strokeColor=#6F0000;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="235" y="560" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-52" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-47" target="9zziB1Dtd-V9IfowJO3V-51" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="540" y="716.31" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-47" value="Gallery Page" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="440" y="705" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-54" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-51" target="9zziB1Dtd-V9IfowJO3V-53" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-51" value="Search By Title &amp;<br>Filter By Tag" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="642" y="698.02" width="120" height="45" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-55" value="Refine Search" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-53" target="9zziB1Dtd-V9IfowJO3V-51" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-57" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-53" target="9zziB1Dtd-V9IfowJO3V-56" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-53" value="Browse Galleries" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="802" y="704.27" width="120" height="32.5" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-59" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-56" target="9zziB1Dtd-V9IfowJO3V-58" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-56" value="Open Gallery" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="802" y="780.52" width="120" height="32.5" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-61" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-58" target="9zziB1Dtd-V9IfowJO3V-60" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-63" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-58" target="9zziB1Dtd-V9IfowJO3V-62" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-65" value="Directional<br>Buttons" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-63" vertex="1" connectable="0">
|
||||
<mxGeometry x="0.1714" relative="1" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-69" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.25;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-58" target="9zziB1Dtd-V9IfowJO3V-68" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="742" y="850.52" />
|
||||
<mxPoint x="742" y="840.52" />
|
||||
<mxPoint x="732" y="840.52" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-58" value="Enlarge Image &amp;&nbsp;<br>Pan/Zoom" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="802" y="840.52" width="120" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-60" value="Download Image" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="802" y="900.52" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-64" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-62" target="9zziB1Dtd-V9IfowJO3V-58" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="672" y="960.52" />
|
||||
<mxPoint x="782" y="960.52" />
|
||||
<mxPoint x="782" y="870.52" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-62" value="Next/Previous<br>Image" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="612" y="890.52" width="120" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-70" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-68" target="9zziB1Dtd-V9IfowJO3V-56" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="762" y="797.39" as="sourcePoint" />
|
||||
<mxPoint x="792" y="796.14" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="762" y="796.52" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-68" value="Close Image" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="642" y="787.52" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-76" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-71" target="9zziB1Dtd-V9IfowJO3V-75" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-78" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-71" target="9zziB1Dtd-V9IfowJO3V-77" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-71" value="Livestream Page" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#d80073;strokeColor=#A50040;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="125" y="780" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-75" value="Watch Stream" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#d80073;strokeColor=#A50040;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="125" y="840" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-77" value="Chat" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#d80073;strokeColor=#A50040;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="12.5" y="780" width="75" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-85" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" source="9zziB1Dtd-V9IfowJO3V-81" target="9zziB1Dtd-V9IfowJO3V-84" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-87" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" source="9zziB1Dtd-V9IfowJO3V-81" target="9zziB1Dtd-V9IfowJO3V-86" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-81" value="Subscriptions Page" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#6a00ff;strokeColor=#3700CC;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="32.5" y="940" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-84" value="View Current Tier" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#6a00ff;strokeColor=#3700CC;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="32.5" y="990" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-89" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" source="9zziB1Dtd-V9IfowJO3V-86" target="9zziB1Dtd-V9IfowJO3V-88" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-86" value="View Available Tiers" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#6a00ff;strokeColor=#3700CC;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="187.5" y="940" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-90" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" source="9zziB1Dtd-V9IfowJO3V-88" target="9zziB1Dtd-V9IfowJO3V-84" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-88" value="Upgrade Tier" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#6a00ff;strokeColor=#3700CC;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="187.5" y="990" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-95" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-91" target="9zziB1Dtd-V9IfowJO3V-94" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="410" y="810" />
|
||||
<mxPoint x="360" y="810" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-100" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-91" target="9zziB1Dtd-V9IfowJO3V-99" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-91" value="Commissions Page" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="350" y="770" width="120" height="27.48" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-97" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-94" target="9zziB1Dtd-V9IfowJO3V-96" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-98" value="Yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-97" vertex="1" connectable="0">
|
||||
<mxGeometry x="-0.6109" relative="1" as="geometry">
|
||||
<mxPoint x="20" y="2" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-105" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-94" target="9zziB1Dtd-V9IfowJO3V-110" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-106" value="No" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-105" vertex="1" connectable="0">
|
||||
<mxGeometry x="-0.1779" y="-2" relative="1" as="geometry">
|
||||
<mxPoint y="-2" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-94" value="Has Existing<br>Commission" style="rhombus;whiteSpace=wrap;html=1;rounded=0;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="310" y="830.52" width="100" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-101" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-96" target="9zziB1Dtd-V9IfowJO3V-99" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-96" value="View Existing<br>Commission Status" style="whiteSpace=wrap;html=1;rounded=0;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="350" y="970" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-99" value="View All Commissions" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="500" y="770" width="130" height="27.48" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-109" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.25;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-104" target="9zziB1Dtd-V9IfowJO3V-96" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="560" y="985" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-104" value="Request New<br>Commission" style="whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;rounded=0;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="470" y="817.52" width="120" height="39.48" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-111" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=1;exitDx=0;exitDy=0;entryX=0.75;entryY=0;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-110" target="9zziB1Dtd-V9IfowJO3V-96" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="440" y="930" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-112" value="No" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-111" vertex="1" connectable="0">
|
||||
<mxGeometry x="0.0044" relative="1" as="geometry">
|
||||
<mxPoint y="5" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-113" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-110" target="9zziB1Dtd-V9IfowJO3V-104" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-114" value="Yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-113" vertex="1" connectable="0">
|
||||
<mxGeometry x="-0.5043" y="1" relative="1" as="geometry">
|
||||
<mxPoint x="3" y="-1" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-110" value="Requests<br>Open" style="rhombus;whiteSpace=wrap;html=1;rounded=0;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="440" y="860.52" width="79.48" height="79.48" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-5" value="public.tags" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
||||
<mxGeometry x="207.5" y="440" width="140" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-6" value="name" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-5">
|
||||
<mxGeometry y="30" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-9" value="public.admins" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
||||
<mxGeometry x="243" y="60" width="175" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-10" value="PK FK Unique uuid user_id" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-9">
|
||||
<mxGeometry y="30" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-11" value="assigned_date:date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-9">
|
||||
<mxGeometry y="60" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-12" value="assigner_id:uuid?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-9">
|
||||
<mxGeometry y="90" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-17" value="auth.users" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#0050ef;fontColor=#ffffff;strokeColor=#001DBC;" vertex="1" parent="1">
|
||||
<mxGeometry x="10" y="120" width="140" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-18" value="id" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-17">
|
||||
<mxGeometry y="30" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-21" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#0050ef;strokeColor=#001DBC;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-18" target="1AZqCnQGpeGdDfHHl4o8-10">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#0050ef;strokeColor=#001DBC;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-18" target="1AZqCnQGpeGdDfHHl4o8-14">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-23" value="public.tier" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#a20025;fontColor=#ffffff;strokeColor=#6F0000;" vertex="1" parent="1">
|
||||
<mxGeometry x="32.5" y="290" width="140" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-24" value="PK name:text" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-23">
|
||||
<mxGeometry y="30" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-25" value="price:decimal" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-23">
|
||||
<mxGeometry y="60" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-26" value="stripe_product_id:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-23">
|
||||
<mxGeometry y="90" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-29" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#a20025;strokeColor=#6F0000;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-24" target="1AZqCnQGpeGdDfHHl4o8-15">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-34" value="public.galleries" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
||||
<mxGeometry x="497.5" y="320" width="140" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-35" value="PK string name" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-34">
|
||||
<mxGeometry y="30" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-37" value="FK tier string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-34">
|
||||
<mxGeometry y="60" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-38" value="string thumbnail_file" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-34">
|
||||
<mxGeometry y="90" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-41" value="bool nsfw" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-34">
|
||||
<mxGeometry y="120" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-39" value="text[] tags" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-34">
|
||||
<mxGeometry y="150" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-42" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#a20025;strokeColor=#6F0000;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-24" target="1AZqCnQGpeGdDfHHl4o8-37">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="207.5" y="335" />
|
||||
<mxPoint x="207.5" y="395" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-43" value="public.skibs" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
||||
<mxGeometry x="499.5" width="140" height="300" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-44" value="PK AI id:&nbsp;int8" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="30" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-46" value="amount:decimal" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="60" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-49" value="user_id:uuid" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="90" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-50" value="request_date:date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="120" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-51" value="accepted:bool?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="150" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-58" value="processed_date:date?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="180" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-52" value="payment_url:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="210" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-57" value="paidDate:date?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="240" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-59" value="completedDate:date?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="270" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-48" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;startArrow=classic;startFill=1;fillColor=#1ba1e2;strokeColor=#006EAF;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-6" target="1AZqCnQGpeGdDfHHl4o8-39">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-56" value="No FK Relationship" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontColor=#FFFFFF;labelBackgroundColor=none;" vertex="1" connectable="0" parent="1AZqCnQGpeGdDfHHl4o8-48">
|
||||
<mxGeometry x="-0.0082" y="2" relative="1" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-13" value="public.user_subscriptions" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
||||
<mxGeometry x="243" y="203" width="175" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-14" value="PK FK Unique user_id:uuid" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-13">
|
||||
<mxGeometry y="30" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-15" value="FK tier:text" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-13">
|
||||
<mxGeometry y="60" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-54" value="active:bool" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-13">
|
||||
<mxGeometry y="90" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-53" value="last_paid_date:date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-13">
|
||||
<mxGeometry y="120" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-47" value="start_date:date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-13">
|
||||
<mxGeometry y="150" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-55" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#0050ef;strokeColor=#001DBC;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-18" target="1AZqCnQGpeGdDfHHl4o8-49">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="197.5" y="165" />
|
||||
<mxPoint x="197.5" y="30" />
|
||||
<mxPoint x="447.5" y="30" />
|
||||
<mxPoint x="447.5" y="105" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-60" value="public.commissions" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
||||
<mxGeometry x="697.5" y="30" width="170" height="330" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-61" value="PK AI id:&nbsp;int8" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="30" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-62" value="amount:decimal" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="60" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-63" value="user_id:uuid" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="90" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-64" value="request_date:date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="120" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-65" value="accepted:bool?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="150" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-66" value="processed_date:date?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="180" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-67" value="hours:number" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="210" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-70" value="pending_approval:bool false" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="240" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-68" value="paidDate:date?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="270" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-69" value="completedDate:date?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="300" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-71" value="public.comission_messages" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
||||
<mxGeometry x="957.5" y="180" width="210" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-72" value="PK AI id:int8" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-71">
|
||||
<mxGeometry y="30" width="210" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-73" value="FK comission_id:int8" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-71">
|
||||
<mxGeometry y="60" width="210" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-74" value="FK sender_id:uuid" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-71">
|
||||
<mxGeometry y="90" width="210" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-79" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#0050ef;strokeColor=#001DBC;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-18" target="1AZqCnQGpeGdDfHHl4o8-63">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="197.5" y="165" />
|
||||
<mxPoint x="197.5" y="30" />
|
||||
<mxPoint x="447.5" y="30" />
|
||||
<mxPoint x="447.5" y="-30" />
|
||||
<mxPoint x="667.5" y="-30" />
|
||||
<mxPoint x="667.5" y="135" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-80" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#1ba1e2;strokeColor=#006EAF;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-61" target="1AZqCnQGpeGdDfHHl4o8-73">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-81" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#0050ef;strokeColor=#001DBC;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-18" target="1AZqCnQGpeGdDfHHl4o8-74">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="197.5" y="165" />
|
||||
<mxPoint x="197.5" y="30" />
|
||||
<mxPoint x="447.5" y="30" />
|
||||
<mxPoint x="447.5" y="-30" />
|
||||
<mxPoint x="667.5" y="-30" />
|
||||
<mxPoint x="667.5" y="400" />
|
||||
<mxPoint x="897.5" y="400" />
|
||||
<mxPoint x="897.5" y="285" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
503
docs/diagrams.drawio
Normal file
@ -0,0 +1,503 @@
|
||||
<mxfile host="Electron" modified="2024-06-04T04:27:06.049Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/24.2.5 Chrome/120.0.6099.109 Electron/28.1.0 Safari/537.36" etag="nuI_NnP1br-4Uuhgp7xU" version="24.2.5" type="device">
|
||||
<diagram name="Page-1" id="F3YAVjulPUqdYhbqfjdd">
|
||||
<mxGraphModel dx="1877" dy="2420" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-28" value="<h1>Database Design</h1>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="332.5" y="-70" width="390" height="10" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-29" value="<h1>UX Flow</h1>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="30" y="620" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-37" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-33" target="9zziB1Dtd-V9IfowJO3V-36" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-33" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="35" y="690" width="30" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-39" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-36" target="9zziB1Dtd-V9IfowJO3V-38" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-36" value="Open Site" style="whiteSpace=wrap;html=1;verticalAlign=top;" parent="1" vertex="1">
|
||||
<mxGeometry x="105" y="705" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-42" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-38" target="9zziB1Dtd-V9IfowJO3V-41" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-43" value="No" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-42" vertex="1" connectable="0">
|
||||
<mxGeometry x="0.2533" y="2" relative="1" as="geometry">
|
||||
<mxPoint x="2" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-48" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-38" target="9zziB1Dtd-V9IfowJO3V-47" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-72" value="Yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-48" vertex="1" connectable="0">
|
||||
<mxGeometry x="-0.2273" relative="1" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-73" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-38" target="9zziB1Dtd-V9IfowJO3V-71" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="265" y="740" />
|
||||
<mxPoint x="265" y="795" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-74" value="Yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-73" vertex="1" connectable="0">
|
||||
<mxGeometry x="0.0411" y="1" relative="1" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-82" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-38" target="9zziB1Dtd-V9IfowJO3V-81" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="305" y="890" />
|
||||
<mxPoint x="93" y="890" />
|
||||
<mxPoint x="93" y="940" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-83" value="Yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-82" vertex="1" connectable="0">
|
||||
<mxGeometry x="-0.8649" y="-1" relative="1" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-92" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-38" target="9zziB1Dtd-V9IfowJO3V-91" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-93" value="Yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-92" vertex="1" connectable="0">
|
||||
<mxGeometry x="-0.6335" y="-2" relative="1" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-38" value="<br>Logged<br>In" style="rhombus;whiteSpace=wrap;html=1;verticalAlign=top;" parent="1" vertex="1">
|
||||
<mxGeometry x="265" y="680" width="80" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-46" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-41" target="9zziB1Dtd-V9IfowJO3V-45" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-41" value="Login/Signup Page" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#a20025;strokeColor=#6F0000;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="245" y="620" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-49" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;dashed=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-45" target="9zziB1Dtd-V9IfowJO3V-47" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-50" value="Click Activation<br>Email Link" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-49" vertex="1" connectable="0">
|
||||
<mxGeometry x="0.1683" y="1" relative="1" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-45" value="Send Confirmation Email" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#a20025;strokeColor=#6F0000;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="235" y="560" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-52" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-47" target="9zziB1Dtd-V9IfowJO3V-51" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="540" y="716.31" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-47" value="Gallery Page" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="440" y="705" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-54" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-51" target="9zziB1Dtd-V9IfowJO3V-53" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-51" value="Search By Title &amp;<br>Filter By Tag" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="642" y="698.02" width="120" height="45" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-55" value="Refine Search" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-53" target="9zziB1Dtd-V9IfowJO3V-51" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-57" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-53" target="9zziB1Dtd-V9IfowJO3V-56" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-53" value="Browse Galleries" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="802" y="704.27" width="120" height="32.5" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-59" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-56" target="9zziB1Dtd-V9IfowJO3V-58" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-56" value="Open Gallery" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="802" y="780.52" width="120" height="32.5" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-61" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-58" target="9zziB1Dtd-V9IfowJO3V-60" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-63" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-58" target="9zziB1Dtd-V9IfowJO3V-62" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-65" value="Directional<br>Buttons" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-63" vertex="1" connectable="0">
|
||||
<mxGeometry x="0.1714" relative="1" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-69" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.25;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-58" target="9zziB1Dtd-V9IfowJO3V-68" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="742" y="850.52" />
|
||||
<mxPoint x="742" y="840.52" />
|
||||
<mxPoint x="732" y="840.52" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-58" value="Enlarge Image &amp;&nbsp;<br>Pan/Zoom" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="802" y="840.52" width="120" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-60" value="Download Image" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="802" y="900.52" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-64" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-62" target="9zziB1Dtd-V9IfowJO3V-58" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="672" y="960.52" />
|
||||
<mxPoint x="782" y="960.52" />
|
||||
<mxPoint x="782" y="870.52" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-62" value="Next/Previous<br>Image" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="612" y="890.52" width="120" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-70" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-68" target="9zziB1Dtd-V9IfowJO3V-56" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="762" y="797.39" as="sourcePoint" />
|
||||
<mxPoint x="792" y="796.14" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="762" y="796.52" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-68" value="Close Image" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="642" y="787.52" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-76" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-71" target="9zziB1Dtd-V9IfowJO3V-75" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-78" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-71" target="9zziB1Dtd-V9IfowJO3V-77" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-71" value="Livestream Page" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#d80073;strokeColor=#A50040;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="125" y="780" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-75" value="Watch Stream" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#d80073;strokeColor=#A50040;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="125" y="840" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-77" value="Chat" style="whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#d80073;strokeColor=#A50040;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="12.5" y="780" width="75" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-85" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" source="9zziB1Dtd-V9IfowJO3V-81" target="9zziB1Dtd-V9IfowJO3V-84" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-87" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" source="9zziB1Dtd-V9IfowJO3V-81" target="9zziB1Dtd-V9IfowJO3V-86" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-81" value="Subscriptions Page" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#6a00ff;strokeColor=#3700CC;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="32.5" y="940" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-84" value="View Current Tier" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#6a00ff;strokeColor=#3700CC;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="32.5" y="990" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-89" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" source="9zziB1Dtd-V9IfowJO3V-86" target="9zziB1Dtd-V9IfowJO3V-88" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-86" value="View Available Tiers" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#6a00ff;strokeColor=#3700CC;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="187.5" y="940" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-90" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" source="9zziB1Dtd-V9IfowJO3V-88" target="9zziB1Dtd-V9IfowJO3V-84" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-88" value="Upgrade Tier" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#6a00ff;strokeColor=#3700CC;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="187.5" y="990" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-95" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-91" target="9zziB1Dtd-V9IfowJO3V-94" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="410" y="810" />
|
||||
<mxPoint x="360" y="810" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-100" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-91" target="9zziB1Dtd-V9IfowJO3V-99" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-91" value="Commissions Page" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="350" y="770" width="120" height="27.48" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-97" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="9zziB1Dtd-V9IfowJO3V-94" target="9zziB1Dtd-V9IfowJO3V-96" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-98" value="Yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-97" vertex="1" connectable="0">
|
||||
<mxGeometry x="-0.6109" relative="1" as="geometry">
|
||||
<mxPoint x="20" y="2" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-105" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-94" target="9zziB1Dtd-V9IfowJO3V-110" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-106" value="No" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-105" vertex="1" connectable="0">
|
||||
<mxGeometry x="-0.1779" y="-2" relative="1" as="geometry">
|
||||
<mxPoint y="-2" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-94" value="Has Existing<br>Commission" style="rhombus;whiteSpace=wrap;html=1;rounded=0;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="310" y="830.52" width="100" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-101" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-96" target="9zziB1Dtd-V9IfowJO3V-99" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-96" value="View Existing<br>Commission Status" style="whiteSpace=wrap;html=1;rounded=0;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="350" y="970" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-99" value="View All Commissions" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="500" y="770" width="130" height="27.48" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-109" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.25;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-104" target="9zziB1Dtd-V9IfowJO3V-96" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="560" y="985" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-104" value="Request New<br>Commission" style="whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;rounded=0;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="470" y="817.52" width="120" height="39.48" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-111" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=1;exitDx=0;exitDy=0;entryX=0.75;entryY=0;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-110" target="9zziB1Dtd-V9IfowJO3V-96" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="440" y="930" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-112" value="No" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-111" vertex="1" connectable="0">
|
||||
<mxGeometry x="0.0044" relative="1" as="geometry">
|
||||
<mxPoint y="5" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-113" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="9zziB1Dtd-V9IfowJO3V-110" target="9zziB1Dtd-V9IfowJO3V-104" edge="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-114" value="Yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="9zziB1Dtd-V9IfowJO3V-113" vertex="1" connectable="0">
|
||||
<mxGeometry x="-0.5043" y="1" relative="1" as="geometry">
|
||||
<mxPoint x="3" y="-1" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9zziB1Dtd-V9IfowJO3V-110" value="Requests<br>Open" style="rhombus;whiteSpace=wrap;html=1;rounded=0;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
|
||||
<mxGeometry x="440" y="860.52" width="79.48" height="79.48" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-5" value="public.tags" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
||||
<mxGeometry x="207.5" y="440" width="140" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-6" value="name" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-5">
|
||||
<mxGeometry y="30" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-9" value="public.admins" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
||||
<mxGeometry x="243" y="60" width="175" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-10" value="PK FK Unique uuid user_id" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-9">
|
||||
<mxGeometry y="30" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-11" value="assigned_date:date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-9">
|
||||
<mxGeometry y="60" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-12" value="assigner_id:uuid?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-9">
|
||||
<mxGeometry y="90" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-17" value="auth.users" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#0050ef;fontColor=#ffffff;strokeColor=#001DBC;" vertex="1" parent="1">
|
||||
<mxGeometry x="10" y="120" width="140" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-18" value="id" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-17">
|
||||
<mxGeometry y="30" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-21" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#0050ef;strokeColor=#001DBC;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-18" target="1AZqCnQGpeGdDfHHl4o8-10">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#0050ef;strokeColor=#001DBC;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-18" target="1AZqCnQGpeGdDfHHl4o8-14">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-23" value="public.tier" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#a20025;fontColor=#ffffff;strokeColor=#6F0000;" vertex="1" parent="1">
|
||||
<mxGeometry x="32.5" y="290" width="140" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-24" value="PK name:text" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-23">
|
||||
<mxGeometry y="30" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-25" value="price:decimal" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-23">
|
||||
<mxGeometry y="60" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-26" value="stripe_product_id:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-23">
|
||||
<mxGeometry y="90" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-29" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#a20025;strokeColor=#6F0000;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-24" target="1AZqCnQGpeGdDfHHl4o8-15">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-34" value="public.galleries" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
||||
<mxGeometry x="497.5" y="320" width="140" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-35" value="PK string name" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-34">
|
||||
<mxGeometry y="30" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-37" value="FK tier string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-34">
|
||||
<mxGeometry y="60" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-38" value="string thumbnail_file" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-34">
|
||||
<mxGeometry y="90" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-41" value="bool nsfw" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-34">
|
||||
<mxGeometry y="120" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-39" value="text[] tags" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-34">
|
||||
<mxGeometry y="150" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-42" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#a20025;strokeColor=#6F0000;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-24" target="1AZqCnQGpeGdDfHHl4o8-37">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="207.5" y="335" />
|
||||
<mxPoint x="207.5" y="395" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-43" value="public.skibs" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
||||
<mxGeometry x="499.5" width="140" height="300" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-44" value="PK AI id:&nbsp;int8" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="30" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-46" value="amount:decimal" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="60" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-49" value="user_id:uuid" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="90" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-50" value="request_date:date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="120" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-51" value="accepted:bool?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="150" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-58" value="processed_date:date?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="180" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-52" value="payment_url:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="210" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-57" value="paidDate:date?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="240" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-59" value="completedDate:date?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-43">
|
||||
<mxGeometry y="270" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-48" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;startArrow=classic;startFill=1;fillColor=#1ba1e2;strokeColor=#006EAF;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-6" target="1AZqCnQGpeGdDfHHl4o8-39">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-56" value="No FK Relationship" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontColor=#FFFFFF;labelBackgroundColor=none;" vertex="1" connectable="0" parent="1AZqCnQGpeGdDfHHl4o8-48">
|
||||
<mxGeometry x="-0.0082" y="2" relative="1" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-13" value="public.user_subscriptions" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
||||
<mxGeometry x="243" y="203" width="175" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-14" value="PK FK Unique user_id:uuid" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-13">
|
||||
<mxGeometry y="30" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-15" value="FK tier:text" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-13">
|
||||
<mxGeometry y="60" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-54" value="active:bool" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-13">
|
||||
<mxGeometry y="90" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-53" value="last_paid_date:date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-13">
|
||||
<mxGeometry y="120" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-47" value="start_date:date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-13">
|
||||
<mxGeometry y="150" width="175" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-55" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#0050ef;strokeColor=#001DBC;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-18" target="1AZqCnQGpeGdDfHHl4o8-49">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="197.5" y="165" />
|
||||
<mxPoint x="197.5" y="30" />
|
||||
<mxPoint x="447.5" y="30" />
|
||||
<mxPoint x="447.5" y="105" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-60" value="public.commissions" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
||||
<mxGeometry x="697.5" y="30" width="170" height="330" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-61" value="PK AI id:&nbsp;int8" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="30" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-62" value="amount:decimal" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="60" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-63" value="user_id:uuid" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="90" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-64" value="request_date:date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="120" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-65" value="accepted:bool?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="150" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-66" value="processed_date:date?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="180" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-67" value="hours:number" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="210" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-70" value="pending_approval:bool false" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="240" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-68" value="paidDate:date?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="270" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-69" value="completedDate:date?" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-60">
|
||||
<mxGeometry y="300" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-71" value="public.comission_messages" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
||||
<mxGeometry x="957.5" y="180" width="210" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-72" value="PK AI id:int8" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-71">
|
||||
<mxGeometry y="30" width="210" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-73" value="FK comission_id:int8" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-71">
|
||||
<mxGeometry y="60" width="210" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-74" value="FK sender_id:uuid" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="1AZqCnQGpeGdDfHHl4o8-71">
|
||||
<mxGeometry y="90" width="210" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-79" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#0050ef;strokeColor=#001DBC;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-18" target="1AZqCnQGpeGdDfHHl4o8-63">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="197.5" y="165" />
|
||||
<mxPoint x="197.5" y="30" />
|
||||
<mxPoint x="447.5" y="30" />
|
||||
<mxPoint x="447.5" y="-30" />
|
||||
<mxPoint x="667.5" y="-30" />
|
||||
<mxPoint x="667.5" y="135" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-80" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#1ba1e2;strokeColor=#006EAF;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-61" target="1AZqCnQGpeGdDfHHl4o8-73">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="1AZqCnQGpeGdDfHHl4o8-81" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#0050ef;strokeColor=#001DBC;" edge="1" parent="1" source="1AZqCnQGpeGdDfHHl4o8-18" target="1AZqCnQGpeGdDfHHl4o8-74">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="197.5" y="165" />
|
||||
<mxPoint x="197.5" y="30" />
|
||||
<mxPoint x="447.5" y="30" />
|
||||
<mxPoint x="447.5" y="-30" />
|
||||
<mxPoint x="667.5" y="-30" />
|
||||
<mxPoint x="667.5" y="400" />
|
||||
<mxPoint x="897.5" y="400" />
|
||||
<mxPoint x="897.5" y="285" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
1109
package-lock.json
generated
@ -9,6 +9,8 @@
|
||||
"@supabase/ssr": "latest",
|
||||
"@supabase/supabase-js": "latest",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@vercel/analytics": "^1.3.1",
|
||||
"@vercel/speed-insights": "^1.0.11",
|
||||
"autoprefixer": "10.4.17",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^11.2.6",
|
||||
@ -19,8 +21,12 @@
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-easy-panzoom": "^0.4.4",
|
||||
"react-masonry-css": "^1.0.16",
|
||||
"react-responsive-masonry": "^2.2.0",
|
||||
"react-select": "^5.8.0",
|
||||
"react-tailwindcss-select": "^1.8.5",
|
||||
"sharp": "^0.33.4",
|
||||
"simplex-noise": "^4.0.1",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwindcss": "3.4.1",
|
||||
|
4
supabase/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Supabase
|
||||
.branches
|
||||
.temp
|
||||
.env
|
@ -1,3 +0,0 @@
|
||||
# Supabase Docker
|
||||
|
||||
This is a minimal Docker Compose setup for self-hosting Supabase. Follow the steps [here](https://supabase.com/docs/guides/hosting/docker) to get started.
|
171
supabase/config.toml
Normal file
@ -0,0 +1,171 @@
|
||||
# A string used to distinguish different Supabase projects on the same host. Defaults to the
|
||||
# working directory name when running `supabase init`.
|
||||
project_id = "neroshitron"
|
||||
|
||||
[api]
|
||||
enabled = true
|
||||
# Port to use for the API URL.
|
||||
port = 54321
|
||||
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
|
||||
# endpoints. `public` is always included.
|
||||
schemas = ["public", "graphql_public"]
|
||||
# Extra schemas to add to the search_path of every request. `public` is always included.
|
||||
extra_search_path = ["public", "extensions"]
|
||||
# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
|
||||
# for accidental or malicious requests.
|
||||
max_rows = 1000
|
||||
|
||||
[db]
|
||||
# Port to use for the local database URL.
|
||||
port = 54322
|
||||
# Port used by db diff command to initialize the shadow database.
|
||||
shadow_port = 54320
|
||||
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
|
||||
# server_version;` on the remote database to check.
|
||||
major_version = 15
|
||||
|
||||
[db.pooler]
|
||||
enabled = false
|
||||
# Port to use for the local connection pooler.
|
||||
port = 54329
|
||||
# Specifies when a server connection can be reused by other clients.
|
||||
# Configure one of the supported pooler modes: `transaction`, `session`.
|
||||
pool_mode = "transaction"
|
||||
# How many server connections to allow per user/database pair.
|
||||
default_pool_size = 20
|
||||
# Maximum number of client connections allowed.
|
||||
max_client_conn = 100
|
||||
|
||||
[realtime]
|
||||
enabled = true
|
||||
# Bind realtime via either IPv4 or IPv6. (default: IPv4)
|
||||
# ip_version = "IPv6"
|
||||
# The maximum length in bytes of HTTP request headers. (default: 4096)
|
||||
# max_header_length = 4096
|
||||
|
||||
[studio]
|
||||
enabled = true
|
||||
# Port to use for Supabase Studio.
|
||||
port = 54323
|
||||
# External URL of the API server that frontend connects to.
|
||||
api_url = "http://127.0.0.1"
|
||||
# OpenAI API Key to use for Supabase AI in the Supabase Studio.
|
||||
openai_api_key = "env(OPENAI_API_KEY)"
|
||||
|
||||
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
|
||||
# are monitored, and you can view the emails that would have been sent from the web interface.
|
||||
[inbucket]
|
||||
enabled = true
|
||||
# Port to use for the email testing server web interface.
|
||||
port = 54324
|
||||
# Uncomment to expose additional ports for testing user applications that send emails.
|
||||
# smtp_port = 54325
|
||||
# pop3_port = 54326
|
||||
|
||||
[storage]
|
||||
enabled = true
|
||||
# The maximum file size allowed (e.g. "5MB", "500KB").
|
||||
file_size_limit = "50MiB"
|
||||
|
||||
[storage.image_transformation]
|
||||
enabled = true
|
||||
|
||||
[auth]
|
||||
enabled = true
|
||||
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
|
||||
# in emails.
|
||||
site_url = "http://127.0.0.1:3000"
|
||||
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
|
||||
additional_redirect_urls = ["https://127.0.0.1:3000"]
|
||||
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
|
||||
jwt_expiry = 3600
|
||||
# If disabled, the refresh token will never expire.
|
||||
enable_refresh_token_rotation = true
|
||||
# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.
|
||||
# Requires enable_refresh_token_rotation = true.
|
||||
refresh_token_reuse_interval = 10
|
||||
# Allow/disallow new user signups to your project.
|
||||
enable_signup = true
|
||||
# Allow/disallow anonymous sign-ins to your project.
|
||||
enable_anonymous_sign_ins = false
|
||||
# Allow/disallow testing manual linking of accounts
|
||||
enable_manual_linking = false
|
||||
|
||||
[auth.email]
|
||||
# Allow/disallow new user signups via email to your project.
|
||||
enable_signup = true
|
||||
# If enabled, a user will be required to confirm any email change on both the old, and new email
|
||||
# addresses. If disabled, only the new email is required to confirm.
|
||||
double_confirm_changes = true
|
||||
# If enabled, users need to confirm their email address before signing in.
|
||||
enable_confirmations = false
|
||||
# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.
|
||||
max_frequency = "1s"
|
||||
|
||||
# Uncomment to customize email template
|
||||
# [auth.email.template.invite]
|
||||
# subject = "You have been invited"
|
||||
# content_path = "./supabase/templates/invite.html"
|
||||
|
||||
[auth.sms]
|
||||
# Allow/disallow new user signups via SMS to your project.
|
||||
enable_signup = true
|
||||
# If enabled, users need to confirm their phone number before signing in.
|
||||
enable_confirmations = false
|
||||
# Template for sending OTP to users
|
||||
template = "Your code is {{ .Code }} ."
|
||||
# Controls the minimum amount of time that must pass before sending another sms otp.
|
||||
max_frequency = "5s"
|
||||
|
||||
# Use pre-defined map of phone number to OTP for testing.
|
||||
# [auth.sms.test_otp]
|
||||
# 4152127777 = "123456"
|
||||
|
||||
# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used.
|
||||
# [auth.hook.custom_access_token]
|
||||
# enabled = true
|
||||
# uri = "pg-functions://<database>/<schema>/<hook_name>"
|
||||
|
||||
# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`.
|
||||
[auth.sms.twilio]
|
||||
enabled = false
|
||||
account_sid = ""
|
||||
message_service_sid = ""
|
||||
# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:
|
||||
auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"
|
||||
|
||||
# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
|
||||
# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`,
|
||||
# `twitter`, `slack`, `spotify`, `workos`, `zoom`.
|
||||
[auth.external.apple]
|
||||
enabled = false
|
||||
client_id = ""
|
||||
# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead:
|
||||
secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"
|
||||
# Overrides the default auth redirectUrl.
|
||||
redirect_uri = ""
|
||||
# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
|
||||
# or any other third-party OIDC providers.
|
||||
url = ""
|
||||
# If enabled, the nonce check will be skipped. Required for local sign in with Google auth.
|
||||
skip_nonce_check = false
|
||||
|
||||
[analytics]
|
||||
enabled = false
|
||||
port = 54327
|
||||
vector_port = 54328
|
||||
# Configure one of the supported backends: `postgres`, `bigquery`.
|
||||
backend = "postgres"
|
||||
|
||||
# Experimental features may be deprecated any time
|
||||
[experimental]
|
||||
# Configures Postgres storage engine to use OrioleDB (S3)
|
||||
orioledb_version = ""
|
||||
# Configures S3 bucket URL, eg. <bucket_name>.s3-<region>.amazonaws.com
|
||||
s3_host = "env(S3_HOST)"
|
||||
# Configures S3 bucket region, eg. us-east-1
|
||||
s3_region = "env(S3_REGION)"
|
||||
# Configures AWS_ACCESS_KEY_ID for S3 bucket
|
||||
s3_access_key = "env(S3_ACCESS_KEY)"
|
||||
# Configures AWS_SECRET_ACCESS_KEY for S3 bucket
|
||||
s3_secret_key = "env(S3_SECRET_KEY)"
|
@ -1,48 +0,0 @@
|
||||
create table profiles (
|
||||
id uuid references auth.users not null,
|
||||
updated_at timestamp with time zone,
|
||||
username text unique,
|
||||
avatar_url text,
|
||||
website text,
|
||||
|
||||
primary key (id),
|
||||
unique(username),
|
||||
constraint username_length check (char_length(username) >= 3)
|
||||
);
|
||||
|
||||
alter table profiles enable row level security;
|
||||
|
||||
create policy "Public profiles are viewable by the owner."
|
||||
on profiles for select
|
||||
using ( auth.uid() = id );
|
||||
|
||||
create policy "Users can insert their own profile."
|
||||
on profiles for insert
|
||||
with check ( auth.uid() = id );
|
||||
|
||||
create policy "Users can update own profile."
|
||||
on profiles for update
|
||||
using ( auth.uid() = id );
|
||||
|
||||
-- Set up Realtime
|
||||
begin;
|
||||
drop publication if exists supabase_realtime;
|
||||
create publication supabase_realtime;
|
||||
commit;
|
||||
alter publication supabase_realtime add table profiles;
|
||||
|
||||
-- Set up Storage
|
||||
insert into storage.buckets (id, name)
|
||||
values ('avatars', 'avatars');
|
||||
|
||||
create policy "Avatar images are publicly accessible."
|
||||
on storage.objects for select
|
||||
using ( bucket_id = 'avatars' );
|
||||
|
||||
create policy "Anyone can upload an avatar."
|
||||
on storage.objects for insert
|
||||
with check ( bucket_id = 'avatars' );
|
||||
|
||||
create policy "Anyone can update an avatar."
|
||||
on storage.objects for update
|
||||
with check ( bucket_id = 'avatars' );
|
@ -1,34 +0,0 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
studio:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: studio/Dockerfile
|
||||
target: dev
|
||||
ports:
|
||||
- 8082:8082
|
||||
mail:
|
||||
container_name: supabase-mail
|
||||
image: inbucket/inbucket:3.0.3
|
||||
ports:
|
||||
- '2500:2500' # SMTP
|
||||
- '9000:9000' # web interface
|
||||
- '1100:1100' # POP3
|
||||
auth:
|
||||
environment:
|
||||
- GOTRUE_SMTP_USER=
|
||||
- GOTRUE_SMTP_PASS=
|
||||
meta:
|
||||
ports:
|
||||
- 5555:8080
|
||||
db:
|
||||
restart: 'no'
|
||||
volumes:
|
||||
# Always use a fresh database when developing
|
||||
- /var/lib/postgresql/data
|
||||
# Seed data should be inserted last (alphabetical order)
|
||||
- ./dev/data.sql:/docker-entrypoint-initdb.d/seed.sql
|
||||
storage:
|
||||
volumes:
|
||||
- /var/lib/storage
|
@ -1,96 +0,0 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
|
||||
minio:
|
||||
image: minio/minio
|
||||
ports:
|
||||
- '9000:9000'
|
||||
- '9001:9001'
|
||||
environment:
|
||||
MINIO_ROOT_USER: supa-storage
|
||||
MINIO_ROOT_PASSWORD: secret1234
|
||||
command: server --console-address ":9001" /data
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "-f", "http://minio:9000/minio/health/live" ]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- ./volumes/storage:/data:z
|
||||
|
||||
minio-createbucket:
|
||||
image: minio/mc
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_healthy
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
/usr/bin/mc alias set supa-minio http://minio:9000 supa-storage secret1234;
|
||||
/usr/bin/mc mb supa-minio/stub;
|
||||
exit 0;
|
||||
"
|
||||
|
||||
storage:
|
||||
container_name: supabase-storage
|
||||
image: supabase/storage-api:v0.43.11
|
||||
depends_on:
|
||||
db:
|
||||
# Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
rest:
|
||||
condition: service_started
|
||||
imgproxy:
|
||||
condition: service_started
|
||||
minio:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"wget",
|
||||
"--no-verbose",
|
||||
"--tries=1",
|
||||
"--spider",
|
||||
"http://localhost:5000/status"
|
||||
]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ANON_KEY: ${ANON_KEY}
|
||||
SERVICE_KEY: ${SERVICE_ROLE_KEY}
|
||||
POSTGREST_URL: http://rest:3000
|
||||
PGRST_JWT_SECRET: ${JWT_SECRET}
|
||||
DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||
FILE_SIZE_LIMIT: 52428800
|
||||
STORAGE_BACKEND: s3
|
||||
GLOBAL_S3_BUCKET: stub
|
||||
GLOBAL_S3_ENDPOINT: http://minio:9000
|
||||
GLOBAL_S3_PROTOCOL: http
|
||||
GLOBAL_S3_FORCE_PATH_STYLE: true
|
||||
AWS_ACCESS_KEY_ID: supa-storage
|
||||
AWS_SECRET_ACCESS_KEY: secret1234
|
||||
AWS_DEFAULT_REGION: stub
|
||||
FILE_STORAGE_BACKEND_PATH: /var/lib/storage
|
||||
TENANT_ID: stub
|
||||
# TODO: https://github.com/supabase/storage-api/issues/55
|
||||
REGION: stub
|
||||
ENABLE_IMAGE_TRANSFORMATION: "true"
|
||||
IMGPROXY_URL: http://imgproxy:5001
|
||||
volumes:
|
||||
- ./volumes/storage:/var/lib/storage:z
|
||||
|
||||
imgproxy:
|
||||
container_name: supabase-imgproxy
|
||||
image: darthsim/imgproxy:v3.8.0
|
||||
healthcheck:
|
||||
test: [ "CMD", "imgproxy", "health" ]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
environment:
|
||||
IMGPROXY_BIND: ":5001"
|
||||
IMGPROXY_USE_ETAG: "true"
|
||||
IMGPROXY_ENABLE_WEBP_DETECTION: ${IMGPROXY_ENABLE_WEBP_DETECTION}
|
@ -1,430 +0,0 @@
|
||||
# Usage
|
||||
# Start: docker compose up
|
||||
# With helpers: docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up
|
||||
# Stop: docker compose down
|
||||
# Destroy: docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml down -v --remove-orphans
|
||||
|
||||
name: supabase
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
studio:
|
||||
container_name: supabase-studio
|
||||
image: supabase/studio:20240422-5cf8f30
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"node",
|
||||
"-e",
|
||||
"require('http').get('http://localhost:3000/api/profile', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"
|
||||
]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
depends_on:
|
||||
analytics:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
STUDIO_PG_META_URL: http://meta:8080
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
|
||||
DEFAULT_ORGANIZATION_NAME: ${STUDIO_DEFAULT_ORGANIZATION}
|
||||
DEFAULT_PROJECT_NAME: ${STUDIO_DEFAULT_PROJECT}
|
||||
|
||||
SUPABASE_URL: http://kong:8000
|
||||
SUPABASE_PUBLIC_URL: ${SUPABASE_PUBLIC_URL}
|
||||
SUPABASE_ANON_KEY: ${ANON_KEY}
|
||||
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
|
||||
|
||||
LOGFLARE_API_KEY: ${LOGFLARE_API_KEY}
|
||||
LOGFLARE_URL: http://analytics:4000
|
||||
NEXT_PUBLIC_ENABLE_LOGS: true
|
||||
# Comment to use Big Query backend for analytics
|
||||
NEXT_ANALYTICS_BACKEND_PROVIDER: postgres
|
||||
# Uncomment to use Big Query backend for analytics
|
||||
# NEXT_ANALYTICS_BACKEND_PROVIDER: bigquery
|
||||
|
||||
kong:
|
||||
container_name: supabase-kong
|
||||
image: kong:2.8.1
|
||||
restart: unless-stopped
|
||||
# https://unix.stackexchange.com/a/294837
|
||||
entrypoint: bash -c 'eval "echo \"$$(cat ~/temp.yml)\"" > ~/kong.yml && /docker-entrypoint.sh kong docker-start'
|
||||
ports:
|
||||
- ${KONG_HTTP_PORT}:8000/tcp
|
||||
- ${KONG_HTTPS_PORT}:8443/tcp
|
||||
depends_on:
|
||||
analytics:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
KONG_DATABASE: "off"
|
||||
KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml
|
||||
# https://github.com/supabase/cli/issues/14
|
||||
KONG_DNS_ORDER: LAST,A,CNAME
|
||||
KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth
|
||||
KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k
|
||||
KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k
|
||||
SUPABASE_ANON_KEY: ${ANON_KEY}
|
||||
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
|
||||
DASHBOARD_USERNAME: ${DASHBOARD_USERNAME}
|
||||
DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD}
|
||||
volumes:
|
||||
# https://github.com/supabase/supabase/issues/12661
|
||||
- ./volumes/api/kong.yml:/home/kong/temp.yml:ro
|
||||
auth:
|
||||
container_name: supabase-auth
|
||||
image: supabase/gotrue:v2.151.0
|
||||
depends_on:
|
||||
db:
|
||||
# Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
analytics:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"wget",
|
||||
"--no-verbose",
|
||||
"--tries=1",
|
||||
"--spider",
|
||||
"http://localhost:9999/health"
|
||||
]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
GOTRUE_API_HOST: 0.0.0.0
|
||||
GOTRUE_API_PORT: 9999
|
||||
API_EXTERNAL_URL: ${API_EXTERNAL_URL}
|
||||
|
||||
GOTRUE_DB_DRIVER: postgres
|
||||
GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||
|
||||
GOTRUE_SITE_URL: ${SITE_URL}
|
||||
GOTRUE_URI_ALLOW_LIST: ${ADDITIONAL_REDIRECT_URLS}
|
||||
GOTRUE_DISABLE_SIGNUP: ${DISABLE_SIGNUP}
|
||||
|
||||
GOTRUE_JWT_ADMIN_ROLES: service_role
|
||||
GOTRUE_JWT_AUD: authenticated
|
||||
GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
|
||||
GOTRUE_JWT_EXP: ${JWT_EXPIRY}
|
||||
GOTRUE_JWT_SECRET: ${JWT_SECRET}
|
||||
|
||||
GOTRUE_EXTERNAL_EMAIL_ENABLED: ${ENABLE_EMAIL_SIGNUP}
|
||||
GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: ${ENABLE_ANONYMOUS_USERS}
|
||||
GOTRUE_MAILER_AUTOCONFIRM: ${ENABLE_EMAIL_AUTOCONFIRM}
|
||||
# GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED: true
|
||||
# GOTRUE_SMTP_MAX_FREQUENCY: 1s
|
||||
GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL}
|
||||
GOTRUE_SMTP_HOST: ${SMTP_HOST}
|
||||
GOTRUE_SMTP_PORT: ${SMTP_PORT}
|
||||
GOTRUE_SMTP_USER: ${SMTP_USER}
|
||||
GOTRUE_SMTP_PASS: ${SMTP_PASS}
|
||||
GOTRUE_SMTP_SENDER_NAME: ${SMTP_SENDER_NAME}
|
||||
GOTRUE_MAILER_URLPATHS_INVITE: ${MAILER_URLPATHS_INVITE}
|
||||
GOTRUE_MAILER_URLPATHS_CONFIRMATION: ${MAILER_URLPATHS_CONFIRMATION}
|
||||
GOTRUE_MAILER_URLPATHS_RECOVERY: ${MAILER_URLPATHS_RECOVERY}
|
||||
GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: ${MAILER_URLPATHS_EMAIL_CHANGE}
|
||||
|
||||
GOTRUE_EXTERNAL_PHONE_ENABLED: ${ENABLE_PHONE_SIGNUP}
|
||||
GOTRUE_SMS_AUTOCONFIRM: ${ENABLE_PHONE_AUTOCONFIRM}
|
||||
|
||||
# Uncomment to enable custom access token hook. You'll need to create a public.custom_access_token_hook function and grant necessary permissions.
|
||||
# See: https://supabase.com/docs/guides/auth/auth-hooks#hook-custom-access-token for details
|
||||
# GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED="true"
|
||||
# GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI="pg-functions://postgres/public/custom_access_token_hook"
|
||||
|
||||
# GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED="true"
|
||||
# GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI="pg-functions://postgres/public/mfa_verification_attempt"
|
||||
|
||||
# GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED="true"
|
||||
# GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="pg-functions://postgres/public/password_verification_attempt"
|
||||
|
||||
|
||||
|
||||
|
||||
rest:
|
||||
container_name: supabase-rest
|
||||
image: postgrest/postgrest:v12.0.1
|
||||
depends_on:
|
||||
db:
|
||||
# Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
analytics:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||
PGRST_DB_SCHEMAS: ${PGRST_DB_SCHEMAS}
|
||||
PGRST_DB_ANON_ROLE: anon
|
||||
PGRST_JWT_SECRET: ${JWT_SECRET}
|
||||
PGRST_DB_USE_LEGACY_GUCS: "false"
|
||||
PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET}
|
||||
PGRST_APP_SETTINGS_JWT_EXP: ${JWT_EXPIRY}
|
||||
command: "postgrest"
|
||||
|
||||
realtime:
|
||||
# This container name looks inconsistent but is correct because realtime constructs tenant id by parsing the subdomain
|
||||
container_name: realtime-dev.supabase-realtime
|
||||
image: supabase/realtime:v2.28.32
|
||||
depends_on:
|
||||
db:
|
||||
# Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
analytics:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"curl",
|
||||
"-sSfL",
|
||||
"--head",
|
||||
"-o",
|
||||
"/dev/null",
|
||||
"-H",
|
||||
"Authorization: Bearer ${ANON_KEY}",
|
||||
"http://localhost:4000/api/tenants/realtime-dev/health"
|
||||
]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PORT: 4000
|
||||
DB_HOST: ${POSTGRES_HOST}
|
||||
DB_PORT: ${POSTGRES_PORT}
|
||||
DB_USER: supabase_admin
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
DB_NAME: ${POSTGRES_DB}
|
||||
DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
|
||||
DB_ENC_KEY: supabaserealtime
|
||||
API_JWT_SECRET: ${JWT_SECRET}
|
||||
FLY_ALLOC_ID: fly123
|
||||
FLY_APP_NAME: realtime
|
||||
SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq
|
||||
ERL_AFLAGS: -proto_dist inet_tcp
|
||||
ENABLE_TAILSCALE: "false"
|
||||
DNS_NODES: "''"
|
||||
command: >
|
||||
sh -c "/app/bin/migrate && /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && /app/bin/server"
|
||||
|
||||
# To use S3 backed storage: docker compose -f docker-compose.yml -f docker-compose.s3.yml up
|
||||
storage:
|
||||
container_name: supabase-storage
|
||||
image: supabase/storage-api:v1.0.6
|
||||
depends_on:
|
||||
db:
|
||||
# Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
rest:
|
||||
condition: service_started
|
||||
imgproxy:
|
||||
condition: service_started
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"wget",
|
||||
"--no-verbose",
|
||||
"--tries=1",
|
||||
"--spider",
|
||||
"http://localhost:5000/status"
|
||||
]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ANON_KEY: ${ANON_KEY}
|
||||
SERVICE_KEY: ${SERVICE_ROLE_KEY}
|
||||
POSTGREST_URL: http://rest:3000
|
||||
PGRST_JWT_SECRET: ${JWT_SECRET}
|
||||
DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||
FILE_SIZE_LIMIT: 52428800
|
||||
STORAGE_BACKEND: file
|
||||
FILE_STORAGE_BACKEND_PATH: /var/lib/storage
|
||||
TENANT_ID: stub
|
||||
# TODO: https://github.com/supabase/storage-api/issues/55
|
||||
REGION: stub
|
||||
GLOBAL_S3_BUCKET: stub
|
||||
ENABLE_IMAGE_TRANSFORMATION: "true"
|
||||
IMGPROXY_URL: http://imgproxy:5001
|
||||
volumes:
|
||||
- ./volumes/storage:/var/lib/storage:z
|
||||
|
||||
imgproxy:
|
||||
container_name: supabase-imgproxy
|
||||
image: darthsim/imgproxy:v3.8.0
|
||||
healthcheck:
|
||||
test: [ "CMD", "imgproxy", "health" ]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
environment:
|
||||
IMGPROXY_BIND: ":5001"
|
||||
IMGPROXY_LOCAL_FILESYSTEM_ROOT: /
|
||||
IMGPROXY_USE_ETAG: "true"
|
||||
IMGPROXY_ENABLE_WEBP_DETECTION: ${IMGPROXY_ENABLE_WEBP_DETECTION}
|
||||
volumes:
|
||||
- ./volumes/storage:/var/lib/storage:z
|
||||
|
||||
meta:
|
||||
container_name: supabase-meta
|
||||
image: supabase/postgres-meta:v0.80.0
|
||||
depends_on:
|
||||
db:
|
||||
# Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
analytics:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PG_META_PORT: 8080
|
||||
PG_META_DB_HOST: ${POSTGRES_HOST}
|
||||
PG_META_DB_PORT: ${POSTGRES_PORT}
|
||||
PG_META_DB_NAME: ${POSTGRES_DB}
|
||||
PG_META_DB_USER: supabase_admin
|
||||
PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
|
||||
functions:
|
||||
container_name: supabase-edge-functions
|
||||
image: supabase/edge-runtime:v1.45.2
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
analytics:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
SUPABASE_URL: http://kong:8000
|
||||
SUPABASE_ANON_KEY: ${ANON_KEY}
|
||||
SUPABASE_SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY}
|
||||
SUPABASE_DB_URL: postgresql://postgres:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||
# TODO: Allow configuring VERIFY_JWT per function. This PR might help: https://github.com/supabase/cli/pull/786
|
||||
VERIFY_JWT: "${FUNCTIONS_VERIFY_JWT}"
|
||||
volumes:
|
||||
- ./volumes/functions:/home/deno/functions:Z
|
||||
command:
|
||||
- start
|
||||
- --main-service
|
||||
- /home/deno/functions/main
|
||||
|
||||
analytics:
|
||||
container_name: supabase-analytics
|
||||
image: supabase/logflare:1.4.0
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "http://localhost:4000/health" ]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
# Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
# Uncomment to use Big Query backend for analytics
|
||||
# volumes:
|
||||
# - type: bind
|
||||
# source: ${PWD}/gcloud.json
|
||||
# target: /opt/app/rel/logflare/bin/gcloud.json
|
||||
# read_only: true
|
||||
environment:
|
||||
LOGFLARE_NODE_HOST: 127.0.0.1
|
||||
DB_USERNAME: supabase_admin
|
||||
DB_DATABASE: ${POSTGRES_DB}
|
||||
DB_HOSTNAME: ${POSTGRES_HOST}
|
||||
DB_PORT: ${POSTGRES_PORT}
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
DB_SCHEMA: _analytics
|
||||
LOGFLARE_API_KEY: ${LOGFLARE_API_KEY}
|
||||
LOGFLARE_SINGLE_TENANT: true
|
||||
LOGFLARE_SUPABASE_MODE: true
|
||||
LOGFLARE_MIN_CLUSTER_SIZE: 1
|
||||
|
||||
# Comment variables to use Big Query backend for analytics
|
||||
POSTGRES_BACKEND_URL: postgresql://supabase_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||
POSTGRES_BACKEND_SCHEMA: _analytics
|
||||
LOGFLARE_FEATURE_FLAG_OVERRIDE: multibackend=true
|
||||
# Uncomment to use Big Query backend for analytics
|
||||
# GOOGLE_PROJECT_ID: ${GOOGLE_PROJECT_ID}
|
||||
# GOOGLE_PROJECT_NUMBER: ${GOOGLE_PROJECT_NUMBER}
|
||||
ports:
|
||||
- 4000:4000
|
||||
|
||||
# Comment out everything below this point if you are using an external Postgres database
|
||||
db:
|
||||
container_name: supabase-db
|
||||
image: supabase/postgres:15.1.1.41
|
||||
healthcheck:
|
||||
test: pg_isready -U postgres -h localhost
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
depends_on:
|
||||
vector:
|
||||
condition: service_healthy
|
||||
command:
|
||||
- postgres
|
||||
- -c
|
||||
- config_file=/etc/postgresql/postgresql.conf
|
||||
- -c
|
||||
- log_min_messages=fatal # prevents Realtime polling queries from appearing in logs
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# Pass down internal port because it's set dynamically by other services
|
||||
- ${POSTGRES_PORT}:${POSTGRES_PORT}
|
||||
environment:
|
||||
POSTGRES_HOST: /var/run/postgresql
|
||||
PGPORT: ${POSTGRES_PORT}
|
||||
POSTGRES_PORT: ${POSTGRES_PORT}
|
||||
PGPASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
PGDATABASE: ${POSTGRES_DB}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
JWT_EXP: ${JWT_EXPIRY}
|
||||
volumes:
|
||||
- ./volumes/db/realtime.sql:/docker-entrypoint-initdb.d/migrations/99-realtime.sql:Z
|
||||
# Must be superuser to create event trigger
|
||||
- ./volumes/db/webhooks.sql:/docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql:Z
|
||||
# Must be superuser to alter reserved role
|
||||
- ./volumes/db/roles.sql:/docker-entrypoint-initdb.d/init-scripts/99-roles.sql:Z
|
||||
# Initialize the database settings with JWT_SECRET and JWT_EXP
|
||||
- ./volumes/db/jwt.sql:/docker-entrypoint-initdb.d/init-scripts/99-jwt.sql:Z
|
||||
# PGDATA directory is persisted between restarts
|
||||
- ./volumes/db/data:/var/lib/postgresql/data:Z
|
||||
# Changes required for Analytics support
|
||||
- ./volumes/db/logs.sql:/docker-entrypoint-initdb.d/migrations/99-logs.sql:Z
|
||||
# Use named volume to persist pgsodium decryption key between restarts
|
||||
- db-config:/etc/postgresql-custom
|
||||
|
||||
vector:
|
||||
container_name: supabase-vector
|
||||
image: timberio/vector:0.28.1-alpine
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
|
||||
"CMD",
|
||||
"wget",
|
||||
"--no-verbose",
|
||||
"--tries=1",
|
||||
"--spider",
|
||||
"http://vector:9001/health"
|
||||
]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
volumes:
|
||||
- ./volumes/logs/vector.yml:/etc/vector/vector.yml:ro
|
||||
- ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock:ro
|
||||
environment:
|
||||
LOGFLARE_API_KEY: ${LOGFLARE_API_KEY}
|
||||
command: [ "--config", "etc/vector/vector.yml" ]
|
||||
|
||||
volumes:
|
||||
db-config:
|
237
supabase/migrations/20240608231854_init.sql
Normal file
@ -0,0 +1,237 @@
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
SET check_function_bodies = false;
|
||||
SET xmloption = content;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_net" WITH SCHEMA "extensions";
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pgsodium" WITH SCHEMA "pgsodium";
|
||||
|
||||
COMMENT ON SCHEMA "public" IS 'standard public schema';
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_graphql" WITH SCHEMA "graphql";
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements" WITH SCHEMA "extensions";
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto" WITH SCHEMA "extensions";
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pgjwt" WITH SCHEMA "extensions";
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "supabase_vault" WITH SCHEMA "vault";
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA "extensions";
|
||||
|
||||
CREATE TYPE "public"."interface_config_option" AS ENUM (
|
||||
'color',
|
||||
'image'
|
||||
);
|
||||
|
||||
ALTER TYPE "public"."interface_config_option" OWNER TO "postgres";
|
||||
|
||||
CREATE TYPE "public"."tier" AS ENUM (
|
||||
'Free',
|
||||
'Tier 1',
|
||||
'Tier 2',
|
||||
'Tier 3'
|
||||
);
|
||||
|
||||
ALTER TYPE "public"."tier" OWNER TO "postgres";
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_table_access_method = "heap";
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "public"."admins" (
|
||||
"user_id" "uuid" NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
|
||||
"assigner" "uuid"
|
||||
);
|
||||
|
||||
ALTER TABLE "public"."admins" OWNER TO "postgres";
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "public"."galleries" (
|
||||
"name" "text" NOT NULL,
|
||||
"column_number" bigint,
|
||||
"tier" "text" DEFAULT 'Free'::"text",
|
||||
"nsfw" boolean,
|
||||
"tags" "text"[],
|
||||
"thumbnail_file" "text" DEFAULT ''::"text"
|
||||
);
|
||||
|
||||
ALTER TABLE "public"."galleries" OWNER TO "postgres";
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "public"."interface_configurations" (
|
||||
"name" "text" NOT NULL,
|
||||
"value" "text" NOT NULL,
|
||||
"type" "public"."interface_config_option" DEFAULT 'color'::"public"."interface_config_option" NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "public"."interface_configurations" OWNER TO "postgres";
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "public"."tags" (
|
||||
"name" "text" NOT NULL,
|
||||
"gallery_name" "text"
|
||||
);
|
||||
|
||||
ALTER TABLE "public"."tags" OWNER TO "postgres";
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "public"."tiers" (
|
||||
"name" "text" NOT NULL,
|
||||
"price" double precision NOT NULL,
|
||||
"color" "text" NOT NULL,
|
||||
"description" "text" NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "public"."tiers" OWNER TO "postgres";
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "public"."user_subscriptions" (
|
||||
"user_id" "uuid" NOT NULL,
|
||||
"tier" "public"."tier"
|
||||
);
|
||||
|
||||
ALTER TABLE "public"."user_subscriptions" OWNER TO "postgres";
|
||||
|
||||
ALTER TABLE ONLY "public"."admins"
|
||||
ADD CONSTRAINT "admins_pkey" PRIMARY KEY ("user_id");
|
||||
|
||||
ALTER TABLE ONLY "public"."galleries"
|
||||
ADD CONSTRAINT "galleries_pkey" PRIMARY KEY ("name");
|
||||
|
||||
ALTER TABLE ONLY "public"."interface_configurations"
|
||||
ADD CONSTRAINT "interface_configurations_pkey" PRIMARY KEY ("name");
|
||||
|
||||
ALTER TABLE ONLY "public"."tags"
|
||||
ADD CONSTRAINT "tags_pkey" PRIMARY KEY ("name");
|
||||
|
||||
ALTER TABLE ONLY "public"."tiers"
|
||||
ADD CONSTRAINT "tiers_pkey" PRIMARY KEY ("name");
|
||||
|
||||
ALTER TABLE ONLY "public"."user_subscriptions"
|
||||
ADD CONSTRAINT "user_subscriptions_pkey" PRIMARY KEY ("user_id");
|
||||
|
||||
ALTER TABLE ONLY "public"."admins"
|
||||
ADD CONSTRAINT "admins_assigner_fkey" FOREIGN KEY ("assigner") REFERENCES "auth"."users"("id");
|
||||
|
||||
ALTER TABLE ONLY "public"."admins"
|
||||
ADD CONSTRAINT "admins_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id");
|
||||
|
||||
ALTER TABLE ONLY "public"."tags"
|
||||
ADD CONSTRAINT "tags_gallery_name_fkey" FOREIGN KEY ("gallery_name") REFERENCES "public"."galleries"("name");
|
||||
|
||||
ALTER TABLE ONLY "public"."user_subscriptions"
|
||||
ADD CONSTRAINT "user_subscriptions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id");
|
||||
|
||||
CREATE POLICY "Enable delete for admins" ON "public"."tags" FOR DELETE TO "authenticated" USING ((EXISTS ( SELECT 1
|
||||
FROM "public"."admins"
|
||||
WHERE ("admins"."user_id" = "auth"."uid"()))));
|
||||
|
||||
CREATE POLICY "Enable delete for users based on admins" ON "public"."galleries" FOR DELETE TO "authenticated" USING ((EXISTS ( SELECT 1
|
||||
FROM "public"."admins"
|
||||
WHERE ("admins"."user_id" = "auth"."uid"()))));
|
||||
|
||||
CREATE POLICY "Enable insert for admins" ON "public"."tags" FOR INSERT TO "authenticated" WITH CHECK ((EXISTS ( SELECT 1
|
||||
FROM "public"."admins"
|
||||
WHERE ("admins"."user_id" = "auth"."uid"()))));
|
||||
|
||||
CREATE POLICY "Enable insert for users based admins" ON "public"."galleries" FOR INSERT WITH CHECK ((EXISTS ( SELECT 1
|
||||
FROM "public"."admins"
|
||||
WHERE ("admins"."user_id" = "auth"."uid"()))));
|
||||
|
||||
CREATE POLICY "Enable read access for all users" ON "public"."admins" FOR SELECT USING (true);
|
||||
|
||||
CREATE POLICY "Enable read access for all users" ON "public"."galleries" FOR SELECT USING (true);
|
||||
|
||||
CREATE POLICY "Enable read access for all users" ON "public"."interface_configurations" FOR SELECT USING (true);
|
||||
|
||||
CREATE POLICY "Enable read access for all users" ON "public"."tags" FOR SELECT USING (true);
|
||||
|
||||
CREATE POLICY "Enable read access for all users" ON "public"."tiers" FOR SELECT USING (true);
|
||||
|
||||
CREATE POLICY "Enable read for users based on user_id" ON "public"."user_subscriptions" FOR SELECT USING (true);
|
||||
|
||||
CREATE POLICY "admin insert" ON "public"."tiers" FOR INSERT WITH CHECK ((EXISTS ( SELECT 1
|
||||
FROM "public"."admins"
|
||||
WHERE ("admins"."user_id" = "auth"."uid"()))));
|
||||
|
||||
CREATE POLICY "admin update" ON "public"."tiers" FOR UPDATE USING ((EXISTS ( SELECT 1
|
||||
FROM "public"."admins"
|
||||
WHERE ("admins"."user_id" = "auth"."uid"()))));
|
||||
|
||||
ALTER TABLE "public"."admins" ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "delete for admin" ON "public"."tiers" FOR DELETE USING ((EXISTS ( SELECT 1
|
||||
FROM "public"."admins"
|
||||
WHERE ("admins"."user_id" = "auth"."uid"()))));
|
||||
|
||||
ALTER TABLE "public"."galleries" ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
ALTER TABLE "public"."interface_configurations" ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
ALTER TABLE "public"."tags" ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
ALTER TABLE "public"."tiers" ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "update for admin" ON "public"."galleries" FOR UPDATE USING ((EXISTS ( SELECT 1
|
||||
FROM "public"."admins"
|
||||
WHERE ("admins"."user_id" = "auth"."uid"()))));
|
||||
|
||||
CREATE POLICY "update theme admin" ON "public"."interface_configurations" FOR UPDATE USING ((EXISTS ( SELECT 1
|
||||
FROM "public"."admins"
|
||||
WHERE ("admins"."user_id" = "auth"."uid"()))));
|
||||
|
||||
ALTER TABLE "public"."user_subscriptions" ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
ALTER PUBLICATION "supabase_realtime" OWNER TO "postgres";
|
||||
|
||||
GRANT USAGE ON SCHEMA "public" TO "postgres";
|
||||
GRANT USAGE ON SCHEMA "public" TO "anon";
|
||||
GRANT USAGE ON SCHEMA "public" TO "authenticated";
|
||||
GRANT USAGE ON SCHEMA "public" TO "service_role";
|
||||
|
||||
GRANT ALL ON TABLE "public"."admins" TO "anon";
|
||||
GRANT ALL ON TABLE "public"."admins" TO "authenticated";
|
||||
GRANT ALL ON TABLE "public"."admins" TO "service_role";
|
||||
|
||||
GRANT ALL ON TABLE "public"."galleries" TO "anon";
|
||||
GRANT ALL ON TABLE "public"."galleries" TO "authenticated";
|
||||
GRANT ALL ON TABLE "public"."galleries" TO "service_role";
|
||||
|
||||
GRANT ALL ON TABLE "public"."interface_configurations" TO "anon";
|
||||
GRANT ALL ON TABLE "public"."interface_configurations" TO "authenticated";
|
||||
GRANT ALL ON TABLE "public"."interface_configurations" TO "service_role";
|
||||
|
||||
GRANT ALL ON TABLE "public"."tags" TO "anon";
|
||||
GRANT ALL ON TABLE "public"."tags" TO "authenticated";
|
||||
GRANT ALL ON TABLE "public"."tags" TO "service_role";
|
||||
|
||||
GRANT ALL ON TABLE "public"."tiers" TO "anon";
|
||||
GRANT ALL ON TABLE "public"."tiers" TO "authenticated";
|
||||
GRANT ALL ON TABLE "public"."tiers" TO "service_role";
|
||||
|
||||
GRANT ALL ON TABLE "public"."user_subscriptions" TO "anon";
|
||||
GRANT ALL ON TABLE "public"."user_subscriptions" TO "authenticated";
|
||||
GRANT ALL ON TABLE "public"."user_subscriptions" TO "service_role";
|
||||
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "postgres";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "anon";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "authenticated";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "service_role";
|
||||
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "postgres";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "anon";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "authenticated";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "service_role";
|
||||
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "postgres";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "anon";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "authenticated";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "service_role";
|
||||
|
||||
RESET ALL;
|
379
supabase/seed.sql
Normal file
@ -0,0 +1,379 @@
|
||||
SET session_replication_role = replica;
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
-- Dumped from database version 15.1 (Ubuntu 15.1-1.pgdg20.04+1)
|
||||
-- Dumped by pg_dump version 15.6 (Ubuntu 15.6-1.pgdg20.04+1)
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
SET check_function_bodies = false;
|
||||
SET xmloption = content;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
--
|
||||
-- Data for Name: audit_log_entries; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
INSERT INTO "auth"."audit_log_entries" ("instance_id", "id", "payload", "created_at", "ip_address") VALUES
|
||||
('00000000-0000-0000-0000-000000000000', 'd337a17f-a756-46eb-a0e8-8ef80fd0510d', '{"action":"user_signedup","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"team","traits":{"provider":"email"}}', '2024-05-27 14:10:29.638476+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'd3e71df0-114a-4490-aeeb-6f92c45bad74', '{"action":"login","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-05-27 14:10:29.64088+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '1eab1cf3-5656-42c2-9e0b-796222de0c55', '{"action":"logout","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account"}', '2024-05-27 14:15:04.733941+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '410c5c63-cba5-442a-a6bb-157acc0bd370', '{"action":"login","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-05-27 14:34:43.53365+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '262365e2-093b-493c-abff-d760344338e3', '{"action":"logout","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account"}', '2024-05-27 15:32:39.855999+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'b3b7c848-8d46-468f-8a9f-be56f842e8f8', '{"action":"login","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-05-27 15:32:48.609554+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '96af268f-e323-43ec-ac10-d33b94c47321', '{"action":"user_invited","actor_id":"00000000-0000-0000-0000-000000000000","actor_username":"service_role","actor_via_sso":false,"log_type":"team","traits":{"user_email":"damienostler1@gmail.com","user_id":"e11e6fda-d1fc-4b0a-bd61-80233fcb5cdd"}}', '2024-05-27 15:44:27.171353+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '98b879f8-3ec0-4b14-9114-e9702693edd7', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-05-27 16:33:16.635885+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '05788b7b-5d97-4449-b036-0d94c021f149', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-05-27 16:33:16.636596+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'ec47cc2d-ee91-4bda-93cc-80966a366550', '{"action":"logout","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account"}', '2024-05-27 17:16:40.455383+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'dee2b80d-2cb6-49b0-9391-d52318eba3c6', '{"action":"login","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-05-27 17:18:49.038768+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '50134af5-eeca-4e0a-93c9-30c93395843d', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-05-27 18:17:16.199899+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'e1b7a78c-c40e-4348-bd41-eadc94a93ebb', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-05-27 18:17:16.200645+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '0fffa887-134b-4d3b-bf70-24246ec38013', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-05-27 19:15:16.230466+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '83236dbe-037b-4a7a-a326-7ff3c9759bd7', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-05-27 19:15:16.231644+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '6f4bb32b-4690-4640-98ac-feb06384ef62', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-05-27 20:13:20.140291+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'e6e8d515-e14c-48ff-8c4d-27cea84d33f3', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-05-27 20:13:20.140793+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'e878d51d-2524-4796-9288-88bcf8d1982b', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-05-27 21:11:27.314657+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '0d3f4871-8215-4ae7-920b-c261ba7eaf11', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-05-27 21:11:27.315598+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'f70081c9-673b-4e8e-84c6-574d4a6fc095', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-05-28 00:06:54.425581+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'ff4e9892-902a-4e6f-9493-976acd87e5ad', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-05-28 00:06:54.426626+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '13891f74-fb79-45bd-a202-3c3846723eb3', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-05-28 01:11:09.34946+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '6125acd2-6bd2-4ae1-8b41-4b54b8097a64', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-05-28 01:11:09.350568+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '904b1ae0-83cb-4bce-a1b9-65f4215e0095', '{"action":"logout","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account"}', '2024-05-28 01:45:53.937368+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '16bba433-9af2-4328-b0d2-d59a9d072edb', '{"action":"user_signedup","actor_id":"e11e6fda-d1fc-4b0a-bd61-80233fcb5cdd","actor_username":"damienostler1@gmail.com","actor_via_sso":false,"log_type":"team","traits":{"provider":"email"}}', '2024-05-28 02:01:35.369669+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'a80de210-524f-41a2-b437-a0a68a5b0dc5', '{"action":"login","actor_id":"e11e6fda-d1fc-4b0a-bd61-80233fcb5cdd","actor_username":"damienostler1@gmail.com","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-05-28 02:01:35.372417+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '56e9bcfc-28ea-4fbf-aae2-a6c611da2536', '{"action":"token_refreshed","actor_id":"e11e6fda-d1fc-4b0a-bd61-80233fcb5cdd","actor_username":"damienostler1@gmail.com","actor_via_sso":false,"log_type":"token"}', '2024-05-28 03:03:11.47339+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'f661df3e-4600-4254-a606-dab87b0bf241', '{"action":"token_revoked","actor_id":"e11e6fda-d1fc-4b0a-bd61-80233fcb5cdd","actor_username":"damienostler1@gmail.com","actor_via_sso":false,"log_type":"token"}', '2024-05-28 03:03:11.474173+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'eccb045f-3fa3-4cc4-92b5-2181ae4c684e', '{"action":"token_refreshed","actor_id":"e11e6fda-d1fc-4b0a-bd61-80233fcb5cdd","actor_username":"damienostler1@gmail.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 01:54:12.341648+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'bdca3f89-401a-40b5-b1bd-7b053f72f15a', '{"action":"token_revoked","actor_id":"e11e6fda-d1fc-4b0a-bd61-80233fcb5cdd","actor_username":"damienostler1@gmail.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 01:54:12.342909+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'a76ea115-0732-45e4-a05f-cbf9aec04dab', '{"action":"logout","actor_id":"e11e6fda-d1fc-4b0a-bd61-80233fcb5cdd","actor_username":"damienostler1@gmail.com","actor_via_sso":false,"log_type":"account"}', '2024-06-01 01:54:44.965028+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '73e58d43-80dc-4c40-93b8-66debb2d79f2', '{"action":"login","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-06-01 01:54:50.928035+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'a7b0b5b0-42d0-4970-a799-f918e320a460', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 02:52:51.499031+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'dd70c686-fc1b-42da-8aa1-91a6d7fbcc14', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 02:52:51.50256+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '09161c6c-3f38-4405-b26e-67fe90bc0005', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 03:51:46.507091+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '7fd17052-1b4a-4178-98b8-3b16b9de930a', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 03:51:46.5081+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '727021f7-1385-43eb-9139-5c8d0984fb07', '{"action":"logout","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account"}', '2024-06-01 04:28:20.313782+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '7df2cdbc-1c84-4847-8142-90477524ee7a', '{"action":"login","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-06-01 04:32:18.045015+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'abb0a2d8-d525-4612-a364-9ba3ea22eabf', '{"action":"logout","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account"}', '2024-06-01 04:32:21.789565+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '21e30d5b-7f03-459f-814d-43757cf3f8bf', '{"action":"login","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-06-01 04:33:07.034577+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '1d8fca22-6ef6-42eb-a1e9-659e702e0568', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 05:31:30.614051+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '3a9c811c-fad9-4739-9e2f-807a65f92ebc', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 05:31:30.614956+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '7620c8d4-9662-4c6b-b04f-8bcf5fc37cd2', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 07:21:44.28552+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '4cfcd7b6-8a95-4713-9409-317734ae7b38', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 07:21:44.286437+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '41cd74f2-8882-4e30-ae82-b743901cae3a', '{"action":"logout","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account"}', '2024-06-01 07:37:28.737433+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'a490a17d-b47e-4820-923d-e7c8bc88efb5', '{"action":"login","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-06-01 07:37:35.302764+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '3c44829d-3ec7-4698-95c3-b53597422759', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 08:36:15.774+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '5b2c215f-bfd1-4b59-a782-eaac62d1c458', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 08:36:15.775505+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '0a5a278a-9fd1-4bcc-a041-66f1302ac7e6', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 09:34:29.572756+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '89b87acd-362b-4c51-8c3a-5a5e356ed8e2', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 09:34:29.573904+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '8c413fec-a827-4ede-a533-2409f855d2e0', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 10:35:29.39825+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '786f5ba3-670c-4799-8b5d-9e2536b75ca0', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 10:35:29.399482+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'da188f51-e5c3-467c-b9c4-7e2ba8430981', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 20:10:45.655684+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'c415ee23-3497-4445-9b18-b3698dd35e16', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 20:10:45.656475+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '4c5c4c36-a506-4c80-b467-68618dbf10db', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 22:05:34.985137+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '52eb3774-6624-4987-b357-fb07ff22e368', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-01 22:05:34.986175+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '4c493923-b97a-4ca7-ad59-801212c59692', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 03:42:48.403833+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '04114bf3-53c5-447b-bb9d-0cf0e97411ce', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 03:42:48.405516+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '59785eb8-3a22-4596-b534-c76d0aaadf9b', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 04:41:14.398806+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '1da856fa-1582-447b-a033-6dfd6e91ee5b', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 04:41:14.399986+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '3790f005-05d8-439d-a828-fb8e6a22356f', '{"action":"logout","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account"}', '2024-06-02 05:20:51.896435+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '903ece4b-635c-4fd0-9a6d-c60103328e11', '{"action":"login","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-06-02 05:21:03.760977+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'ab8d42bd-c9df-4782-8cbd-14279592fe94', '{"action":"logout","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account"}', '2024-06-02 06:18:43.051255+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '008ae3d1-61c4-4456-8e70-8a2030acbbc4', '{"action":"login","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-06-02 07:19:23.344522+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'bf042910-d447-4f27-bb63-fbd6b80c266f', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 08:17:36.070061+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '55f64c6a-d6bd-4dd4-8627-3c35caa6dc7e', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 08:17:36.071183+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '578dc04e-5665-410c-8c0e-bbf40b397b18', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 09:23:17.276185+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '4ccf2be5-8ecd-48ee-9400-7fef1004dc56', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 09:23:17.277357+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '824ccdf1-4dc0-4cee-8c01-ac66cdea101f', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 10:21:37.85499+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '98aa3855-f2ff-4118-9618-041c82318963', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 10:21:37.856028+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'f1599f7e-e1c1-4861-87e5-9b8e5e9fa8f7', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 11:20:00.113655+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '84f7562d-151d-4e30-b810-70891f3a8a2b', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 11:20:00.115274+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '7451f114-d011-4552-9b21-bcf6d6df6243', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 20:49:23.877784+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '24b82857-e2fd-4732-a2e8-ac99f67fffa9', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 20:49:23.878598+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'bf2935a0-244a-449d-925c-ca7ad6bf91d6', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 21:49:06.535623+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '88b130fb-5418-49ec-9e54-7db1df9b9353', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 21:49:06.536554+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '2aafc337-642d-4f4c-b550-b1c88b0f698a', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 22:47:33.949246+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '7b7bc3c1-3751-4e00-a08c-14b5bb3cf892', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-02 22:47:33.950537+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '7b3b5a37-ed60-4b75-84ae-0caba9254581', '{"action":"login","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-06-03 00:10:51.713229+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'd984ff2d-6d96-43d9-9a39-703de7400fd1', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-03 01:10:47.663131+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'e996a172-f818-489a-84b3-8dc8b8a1068b', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-03 01:10:47.663668+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '0f07359e-4c78-40a7-8711-4127dc74c0fb', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-03 02:08:53.524332+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'b8f1d6e8-4e3f-473c-b0be-98f4540da6f3', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-03 02:08:53.525533+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '1043d8ee-0301-46e0-a451-2bb043fe6634', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-03 03:07:09.462821+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '6924eb3a-0588-46ff-94dc-b45f29a06e74', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-03 03:07:09.463674+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '3995cc04-3976-47bd-aafb-04fcf8a533db', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-03 04:05:10.557103+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'd5cb3c9b-aa2d-4ca6-9067-4c31ecbb7827', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-03 04:05:10.558474+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '8c7ca4b6-0b87-4704-9508-8cdfaac13c65', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-03 05:04:07.678559+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '7a1eb770-9f3c-4ac2-a13a-fafe80704bef', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-03 05:04:07.679535+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'c4c2ae8b-62eb-4dde-8023-bb4a52fd8402', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-03 06:02:18.158106+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'f3deb712-a27b-4652-b3dc-bb782188943c', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-03 06:02:18.159666+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'db502a30-3a84-4151-8c02-7be7eba0f8d1', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-04 00:29:24.732764+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'a24f4f60-00aa-48eb-8318-ed131fd44b2e', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-04 00:29:24.734165+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'f1d4e360-3355-4850-9f9e-64961bb1cadf', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-04 01:27:58.415574+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '45e4135f-e65c-4011-bd07-c6a5bcb33860', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-04 01:27:58.416715+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '95c4b03d-1f15-4ad9-83c1-79f079407908', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-04 02:26:28.440011+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '9b5db737-976a-459f-8f12-eed61184218d', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-04 02:26:28.441438+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '2bbe5699-d14f-4c3f-9c87-52b7f01ed087', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-05 00:52:31.052995+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'b54ff1b9-6254-43d2-a210-b5d9d2701974', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-05 00:52:31.055077+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'b0611a51-8bb8-4fab-937e-68a413e02b5e', '{"action":"logout","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account"}', '2024-06-05 01:03:30.844197+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '57f72aa6-2609-4efe-bf06-950681c975f9', '{"action":"login","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-06-05 01:03:39.551264+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '3e6b6d3e-c8bd-4cd6-a7ac-b55c59269df1', '{"action":"login","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"account","traits":{"provider":"email"}}', '2024-06-05 01:06:08.194567+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '59805a05-bfba-4dbe-8021-8db6d6b71a4e', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-05 02:02:22.65237+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'eaf050ba-9bdc-4771-8dcb-1534c38d68b9', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-05 02:02:22.653268+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'a326a1f9-92f7-472c-bbb7-f10c6f3c1dff', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-05 03:00:26.781265+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '473362fc-dc06-43d5-99df-065c9e671347', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-05 03:00:26.782565+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '3a82cdde-76bf-4cc4-a5e2-b818601100a1', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-07 23:35:28.284863+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'c3ecf585-5984-4b7e-8115-f57523a31e23', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-07 23:35:28.285858+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'cc3425df-b1e8-4e7b-8729-80e66eae2b27', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-08 00:33:56.442635+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'd991bd91-d600-457c-a887-67a3be188c83', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-08 00:33:56.44418+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'a8bd8737-8115-4643-b9a7-f288be170b28', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-08 02:07:59.069381+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '495056f7-149e-43b3-bc58-1712e3787215', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-08 02:07:59.070256+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '27ca0341-280d-474a-a3da-39fa4c69748e', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-08 03:06:06.149018+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'aa06c3b0-cd66-440a-a184-d44795552f11', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-08 03:06:06.15067+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '398046e7-2dc1-49e0-92ad-4823c0001271', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-08 04:04:06.664163+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', 'eb481c2d-2f96-4efa-84cb-e715ee547913', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-08 04:04:06.665465+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '088f4537-0025-44e9-a499-dc85d7034ed3', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-08 22:08:31.582227+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '7f476998-602e-4d2e-9129-92ea021b1d50', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-08 22:08:31.583859+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '1c9bf13b-7ecf-4432-a83e-8dcd95eeb6c9', '{"action":"token_refreshed","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-08 23:06:38.987497+00', ''),
|
||||
('00000000-0000-0000-0000-000000000000', '07c85a08-4a4b-4238-ac4c-047350db4b7f', '{"action":"token_revoked","actor_id":"893c7701-d5df-4415-80bd-1ec089764400","actor_username":"damienostler1@outlook.com","actor_via_sso":false,"log_type":"token"}', '2024-06-08 23:06:38.988275+00', '');
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: flow_state; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: users; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
INSERT INTO "auth"."users" ("instance_id", "id", "aud", "role", "email", "encrypted_password", "email_confirmed_at", "invited_at", "confirmation_token", "confirmation_sent_at", "recovery_token", "recovery_sent_at", "email_change_token_new", "email_change", "email_change_sent_at", "last_sign_in_at", "raw_app_meta_data", "raw_user_meta_data", "is_super_admin", "created_at", "updated_at", "phone", "phone_confirmed_at", "phone_change", "phone_change_token", "phone_change_sent_at", "email_change_token_current", "email_change_confirm_status", "banned_until", "reauthentication_token", "reauthentication_sent_at", "is_sso_user", "deleted_at", "is_anonymous") VALUES
|
||||
('00000000-0000-0000-0000-000000000000', 'e11e6fda-d1fc-4b0a-bd61-80233fcb5cdd', 'authenticated', 'authenticated', 'damienostler1@gmail.com', '', '2024-05-28 02:01:35.370491+00', '2024-05-27 15:44:27.171961+00', '', '2024-05-27 15:44:27.171961+00', '', NULL, '', '', NULL, '2024-05-28 02:01:35.372861+00', '{"provider": "email", "providers": ["email"]}', '{}', NULL, '2024-05-27 15:44:27.167772+00', '2024-06-01 01:54:12.344577+00', NULL, NULL, '', '', NULL, '', 0, NULL, '', NULL, false, NULL, false),
|
||||
('00000000-0000-0000-0000-000000000000', '893c7701-d5df-4415-80bd-1ec089764400', 'authenticated', 'authenticated', 'damienostler1@outlook.com', '$2a$10$ISYdoWsKL7gxfRz7c5IKDOTsmcjNpGgg9OOApYLMOvtOoNTo4HGM6', '2024-05-27 14:10:29.639017+00', NULL, '', NULL, '', NULL, '', '', NULL, '2024-06-05 01:06:08.194961+00', '{"provider": "email", "providers": ["email"]}', '{"sub": "893c7701-d5df-4415-80bd-1ec089764400", "email": "damienostler1@outlook.com", "email_verified": false, "phone_verified": false}', NULL, '2024-05-27 14:10:29.634157+00', '2024-06-08 23:06:38.990134+00', NULL, NULL, '', '', NULL, '', 0, NULL, '', NULL, false, NULL, false);
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: identities; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
INSERT INTO "auth"."identities" ("provider_id", "user_id", "identity_data", "provider", "last_sign_in_at", "created_at", "updated_at", "id") VALUES
|
||||
('893c7701-d5df-4415-80bd-1ec089764400', '893c7701-d5df-4415-80bd-1ec089764400', '{"sub": "893c7701-d5df-4415-80bd-1ec089764400", "email": "damienostler1@outlook.com", "email_verified": false, "phone_verified": false}', 'email', '2024-05-27 14:10:29.636992+00', '2024-05-27 14:10:29.637013+00', '2024-05-27 14:10:29.637013+00', 'b823bde7-9eae-4e1f-8253-75f12f0f06f2'),
|
||||
('e11e6fda-d1fc-4b0a-bd61-80233fcb5cdd', 'e11e6fda-d1fc-4b0a-bd61-80233fcb5cdd', '{"sub": "e11e6fda-d1fc-4b0a-bd61-80233fcb5cdd", "email": "damienostler1@gmail.com", "email_verified": false, "phone_verified": false}', 'email', '2024-05-27 15:44:27.170495+00', '2024-05-27 15:44:27.170543+00', '2024-05-27 15:44:27.170543+00', 'f88a9c9a-1566-4c04-8340-3d233dcca864');
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: instances; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: sessions; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
INSERT INTO "auth"."sessions" ("id", "user_id", "created_at", "updated_at", "factor_id", "aal", "not_after", "refreshed_at", "user_agent", "ip", "tag") VALUES
|
||||
('88176780-1709-4810-92de-36f909e3fafb', '893c7701-d5df-4415-80bd-1ec089764400', '2024-06-05 01:06:08.19499+00', '2024-06-05 01:06:08.19499+00', NULL, 'aal1', NULL, NULL, 'node', '192.168.65.1', NULL),
|
||||
('8de4dae9-a9f9-44ad-bf2a-c42356feb0e2', '893c7701-d5df-4415-80bd-1ec089764400', '2024-06-05 01:03:39.551694+00', '2024-06-08 23:06:38.991049+00', NULL, 'aal1', NULL, '2024-06-08 23:06:38.990997', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', '192.168.65.1', NULL);
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: mfa_amr_claims; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
INSERT INTO "auth"."mfa_amr_claims" ("session_id", "created_at", "updated_at", "authentication_method", "id") VALUES
|
||||
('8de4dae9-a9f9-44ad-bf2a-c42356feb0e2', '2024-06-05 01:03:39.553156+00', '2024-06-05 01:03:39.553156+00', 'password', '70b28b1d-9c1c-42ed-b9b6-4629b4dc6610'),
|
||||
('88176780-1709-4810-92de-36f909e3fafb', '2024-06-05 01:06:08.195749+00', '2024-06-05 01:06:08.195749+00', 'password', '2d748036-15f9-49a1-ba18-a9636803f795');
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: mfa_factors; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: mfa_challenges; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: one_time_tokens; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: refresh_tokens; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
INSERT INTO "auth"."refresh_tokens" ("instance_id", "id", "token", "user_id", "revoked", "created_at", "updated_at", "parent", "session_id") VALUES
|
||||
('00000000-0000-0000-0000-000000000000', 83, 'c6bYIcVnSBosWtn4QX8F6w', '893c7701-d5df-4415-80bd-1ec089764400', false, '2024-06-05 01:06:08.195296+00', '2024-06-05 01:06:08.195296+00', NULL, '88176780-1709-4810-92de-36f909e3fafb'),
|
||||
('00000000-0000-0000-0000-000000000000', 82, 'vgibpWzclaC1SiIWnkWFTw', '893c7701-d5df-4415-80bd-1ec089764400', true, '2024-06-05 01:03:39.55241+00', '2024-06-05 02:02:22.653721+00', NULL, '8de4dae9-a9f9-44ad-bf2a-c42356feb0e2'),
|
||||
('00000000-0000-0000-0000-000000000000', 84, 'kcsINvcmhD18guzedNei0Q', '893c7701-d5df-4415-80bd-1ec089764400', true, '2024-06-05 02:02:22.654159+00', '2024-06-05 03:00:26.783216+00', 'vgibpWzclaC1SiIWnkWFTw', '8de4dae9-a9f9-44ad-bf2a-c42356feb0e2'),
|
||||
('00000000-0000-0000-0000-000000000000', 85, '4eJVeDMlvmAUk20ZqwdGQA', '893c7701-d5df-4415-80bd-1ec089764400', true, '2024-06-05 03:00:26.784329+00', '2024-06-07 23:35:28.286039+00', 'kcsINvcmhD18guzedNei0Q', '8de4dae9-a9f9-44ad-bf2a-c42356feb0e2'),
|
||||
('00000000-0000-0000-0000-000000000000', 86, 'Nb0zuvJjDL8keK1lF1xX9Q', '893c7701-d5df-4415-80bd-1ec089764400', true, '2024-06-07 23:35:28.28691+00', '2024-06-08 00:33:56.445064+00', '4eJVeDMlvmAUk20ZqwdGQA', '8de4dae9-a9f9-44ad-bf2a-c42356feb0e2'),
|
||||
('00000000-0000-0000-0000-000000000000', 119, 'y9oA8pEThLtPyEJW2IK3eA', '893c7701-d5df-4415-80bd-1ec089764400', true, '2024-06-08 00:33:56.446127+00', '2024-06-08 02:07:59.070607+00', 'Nb0zuvJjDL8keK1lF1xX9Q', '8de4dae9-a9f9-44ad-bf2a-c42356feb0e2'),
|
||||
('00000000-0000-0000-0000-000000000000', 120, 'TwWGx47OPQs1BNtkLs9JSA', '893c7701-d5df-4415-80bd-1ec089764400', true, '2024-06-08 02:07:59.07107+00', '2024-06-08 03:06:06.151386+00', 'y9oA8pEThLtPyEJW2IK3eA', '8de4dae9-a9f9-44ad-bf2a-c42356feb0e2'),
|
||||
('00000000-0000-0000-0000-000000000000', 121, 'mL_eZQCxVdYq7SR-UjEJgQ', '893c7701-d5df-4415-80bd-1ec089764400', true, '2024-06-08 03:06:06.154686+00', '2024-06-08 04:04:06.666124+00', 'TwWGx47OPQs1BNtkLs9JSA', '8de4dae9-a9f9-44ad-bf2a-c42356feb0e2'),
|
||||
('00000000-0000-0000-0000-000000000000', 122, 'cFLCDOB1QfKd_ydDNLRmoQ', '893c7701-d5df-4415-80bd-1ec089764400', true, '2024-06-08 04:04:06.667264+00', '2024-06-08 22:08:31.584256+00', 'mL_eZQCxVdYq7SR-UjEJgQ', '8de4dae9-a9f9-44ad-bf2a-c42356feb0e2'),
|
||||
('00000000-0000-0000-0000-000000000000', 123, 'G5KZsQQhC6kFqd0Xht2ypg', '893c7701-d5df-4415-80bd-1ec089764400', true, '2024-06-08 22:08:31.584969+00', '2024-06-08 23:06:38.988706+00', 'cFLCDOB1QfKd_ydDNLRmoQ', '8de4dae9-a9f9-44ad-bf2a-c42356feb0e2'),
|
||||
('00000000-0000-0000-0000-000000000000', 124, '4p4Q-c4t1ZH2PUvl_mcYtQ', '893c7701-d5df-4415-80bd-1ec089764400', false, '2024-06-08 23:06:38.989251+00', '2024-06-08 23:06:38.989251+00', 'G5KZsQQhC6kFqd0Xht2ypg', '8de4dae9-a9f9-44ad-bf2a-c42356feb0e2');
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: sso_providers; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: saml_providers; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: saml_relay_states; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: sso_domains; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: key; Type: TABLE DATA; Schema: pgsodium; Owner: supabase_admin
|
||||
--
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: admins; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
INSERT INTO "public"."admins" ("user_id", "created_at", "assigner") VALUES
|
||||
('893c7701-d5df-4415-80bd-1ec089764400', '2024-06-02 21:12:58.507093+00', NULL);
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: galleries; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
INSERT INTO "public"."galleries" ("name", "column_number", "tier", "nsfw", "tags", "thumbnail_file") VALUES
|
||||
('Test', 3, 'Free', false, '{twatwat}', ''),
|
||||
('Testsdadas', 3, 'Free', false, '{}', '');
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: interface_configurations; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
INSERT INTO "public"."interface_configurations" ("name", "value", "type") VALUES
|
||||
('primary-dark', '#100120', 'color'),
|
||||
('secondary-light', '#6F5D90', 'color'),
|
||||
('secondary-dark', '#2F1D50', 'color'),
|
||||
('primary', '#18161d', 'color'),
|
||||
('secondary', '#4F3D70', 'color'),
|
||||
('primary-light', '#403260', 'color'),
|
||||
('error-dark', '#5C0D0D', 'color'),
|
||||
('error', '#862117', 'color'),
|
||||
('success-dark', '#00A986', 'color'),
|
||||
('error-light', '#C44C4C', 'color'),
|
||||
('success-light', '#20E9C6', 'color'),
|
||||
('success', '#00C9A6', 'color'),
|
||||
('warning', '#E17558', 'color'),
|
||||
('warning-light', '#E39578', 'color'),
|
||||
('info-light', '#424260', 'color'),
|
||||
('warning-dark', '#C15538', 'color'),
|
||||
('info-dark', '#020120', 'color'),
|
||||
('info', '#222140', 'color');
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: tags; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
INSERT INTO "public"."tags" ("name", "gallery_name") VALUES
|
||||
('twatwat', NULL),
|
||||
('', NULL);
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: tiers; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
INSERT INTO "public"."tiers" ("name", "price", "color", "description") VALUES
|
||||
('Free', 0, '#8f6eaa', 'This is a test tier.dasdasd');
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: user_subscriptions; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
INSERT INTO "public"."user_subscriptions" ("user_id", "tier") VALUES
|
||||
('893c7701-d5df-4415-80bd-1ec089764400', 'Tier 2');
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: objects; Type: TABLE DATA; Schema: storage; Owner: supabase_storage_admin
|
||||
--
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: s3_multipart_uploads; Type: TABLE DATA; Schema: storage; Owner: supabase_storage_admin
|
||||
--
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: s3_multipart_uploads_parts; Type: TABLE DATA; Schema: storage; Owner: supabase_storage_admin
|
||||
--
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: hooks; Type: TABLE DATA; Schema: supabase_functions; Owner: supabase_functions_admin
|
||||
--
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: secrets; Type: TABLE DATA; Schema: vault; Owner: supabase_admin
|
||||
--
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Name: refresh_tokens_id_seq; Type: SEQUENCE SET; Schema: auth; Owner: supabase_auth_admin
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('"auth"."refresh_tokens_id_seq"', 124, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: key_key_id_seq; Type: SEQUENCE SET; Schema: pgsodium; Owner: supabase_admin
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('"pgsodium"."key_key_id_seq"', 1, false);
|
||||
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
RESET ALL;
|
@ -1,241 +0,0 @@
|
||||
_format_version: '2.1'
|
||||
_transform: true
|
||||
|
||||
###
|
||||
### Consumers / Users
|
||||
###
|
||||
consumers:
|
||||
- username: DASHBOARD
|
||||
- username: anon
|
||||
keyauth_credentials:
|
||||
- key: $SUPABASE_ANON_KEY
|
||||
- username: service_role
|
||||
keyauth_credentials:
|
||||
- key: $SUPABASE_SERVICE_KEY
|
||||
|
||||
###
|
||||
### Access Control List
|
||||
###
|
||||
acls:
|
||||
- consumer: anon
|
||||
group: anon
|
||||
- consumer: service_role
|
||||
group: admin
|
||||
|
||||
###
|
||||
### Dashboard credentials
|
||||
###
|
||||
basicauth_credentials:
|
||||
- consumer: DASHBOARD
|
||||
username: $DASHBOARD_USERNAME
|
||||
password: $DASHBOARD_PASSWORD
|
||||
|
||||
###
|
||||
### API Routes
|
||||
###
|
||||
services:
|
||||
## Open Auth routes
|
||||
- name: auth-v1-open
|
||||
url: http://auth:9999/verify
|
||||
routes:
|
||||
- name: auth-v1-open
|
||||
strip_path: true
|
||||
paths:
|
||||
- /auth/v1/verify
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: auth-v1-open-callback
|
||||
url: http://auth:9999/callback
|
||||
routes:
|
||||
- name: auth-v1-open-callback
|
||||
strip_path: true
|
||||
paths:
|
||||
- /auth/v1/callback
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: auth-v1-open-authorize
|
||||
url: http://auth:9999/authorize
|
||||
routes:
|
||||
- name: auth-v1-open-authorize
|
||||
strip_path: true
|
||||
paths:
|
||||
- /auth/v1/authorize
|
||||
plugins:
|
||||
- name: cors
|
||||
|
||||
## Secure Auth routes
|
||||
- name: auth-v1
|
||||
_comment: 'GoTrue: /auth/v1/* -> http://auth:9999/*'
|
||||
url: http://auth:9999/
|
||||
routes:
|
||||
- name: auth-v1-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /auth/v1/
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: key-auth
|
||||
config:
|
||||
hide_credentials: false
|
||||
- name: acl
|
||||
config:
|
||||
hide_groups_header: true
|
||||
allow:
|
||||
- admin
|
||||
- anon
|
||||
|
||||
## Secure REST routes
|
||||
- name: rest-v1
|
||||
_comment: 'PostgREST: /rest/v1/* -> http://rest:3000/*'
|
||||
url: http://rest:3000/
|
||||
routes:
|
||||
- name: rest-v1-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /rest/v1/
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: key-auth
|
||||
config:
|
||||
hide_credentials: true
|
||||
- name: acl
|
||||
config:
|
||||
hide_groups_header: true
|
||||
allow:
|
||||
- admin
|
||||
- anon
|
||||
|
||||
## Secure GraphQL routes
|
||||
- name: graphql-v1
|
||||
_comment: 'PostgREST: /graphql/v1/* -> http://rest:3000/rpc/graphql'
|
||||
url: http://rest:3000/rpc/graphql
|
||||
routes:
|
||||
- name: graphql-v1-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /graphql/v1
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: key-auth
|
||||
config:
|
||||
hide_credentials: true
|
||||
- name: request-transformer
|
||||
config:
|
||||
add:
|
||||
headers:
|
||||
- Content-Profile:graphql_public
|
||||
- name: acl
|
||||
config:
|
||||
hide_groups_header: true
|
||||
allow:
|
||||
- admin
|
||||
- anon
|
||||
|
||||
## Secure Realtime routes
|
||||
- name: realtime-v1-ws
|
||||
_comment: 'Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*'
|
||||
url: http://realtime-dev.supabase-realtime:4000/socket
|
||||
protocol: ws
|
||||
routes:
|
||||
- name: realtime-v1-ws
|
||||
strip_path: true
|
||||
paths:
|
||||
- /realtime/v1/
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: key-auth
|
||||
config:
|
||||
hide_credentials: false
|
||||
- name: acl
|
||||
config:
|
||||
hide_groups_header: true
|
||||
allow:
|
||||
- admin
|
||||
- anon
|
||||
- name: realtime-v1-rest
|
||||
_comment: 'Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*'
|
||||
url: http://realtime-dev.supabase-realtime:4000/api
|
||||
protocol: http
|
||||
routes:
|
||||
- name: realtime-v1-rest
|
||||
strip_path: true
|
||||
paths:
|
||||
- /realtime/v1/api
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: key-auth
|
||||
config:
|
||||
hide_credentials: false
|
||||
- name: acl
|
||||
config:
|
||||
hide_groups_header: true
|
||||
allow:
|
||||
- admin
|
||||
- anon
|
||||
## Storage routes: the storage server manages its own auth
|
||||
- name: storage-v1
|
||||
_comment: 'Storage: /storage/v1/* -> http://storage:5000/*'
|
||||
url: http://storage:5000/
|
||||
routes:
|
||||
- name: storage-v1-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /storage/v1/
|
||||
plugins:
|
||||
- name: cors
|
||||
|
||||
## Edge Functions routes
|
||||
- name: functions-v1
|
||||
_comment: 'Edge Functions: /functions/v1/* -> http://functions:9000/*'
|
||||
url: http://functions:9000/
|
||||
routes:
|
||||
- name: functions-v1-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /functions/v1/
|
||||
plugins:
|
||||
- name: cors
|
||||
|
||||
## Analytics routes
|
||||
- name: analytics-v1
|
||||
_comment: 'Analytics: /analytics/v1/* -> http://logflare:4000/*'
|
||||
url: http://analytics:4000/
|
||||
routes:
|
||||
- name: analytics-v1-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /analytics/v1/
|
||||
|
||||
## Secure Database routes
|
||||
- name: meta
|
||||
_comment: 'pg-meta: /pg/* -> http://pg-meta:8080/*'
|
||||
url: http://meta:8080/
|
||||
routes:
|
||||
- name: meta-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /pg/
|
||||
plugins:
|
||||
- name: key-auth
|
||||
config:
|
||||
hide_credentials: false
|
||||
- name: acl
|
||||
config:
|
||||
hide_groups_header: true
|
||||
allow:
|
||||
- admin
|
||||
|
||||
## Protected Dashboard - catch all remaining routes
|
||||
- name: dashboard
|
||||
_comment: 'Studio: /* -> http://studio:3000/*'
|
||||
url: http://studio:3000/
|
||||
routes:
|
||||
- name: dashboard-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: basic-auth
|
||||
config:
|
||||
hide_credentials: true
|
@ -1,5 +0,0 @@
|
||||
\set jwt_secret `echo "$JWT_SECRET"`
|
||||
\set jwt_exp `echo "$JWT_EXP"`
|
||||
|
||||
ALTER DATABASE postgres SET "app.settings.jwt_secret" TO :'jwt_secret';
|
||||
ALTER DATABASE postgres SET "app.settings.jwt_exp" TO :'jwt_exp';
|
@ -1,4 +0,0 @@
|
||||
\set pguser `echo "$POSTGRES_USER"`
|
||||
|
||||
create schema if not exists _analytics;
|
||||
alter schema _analytics owner to :pguser;
|
@ -1,4 +0,0 @@
|
||||
\set pguser `echo "$POSTGRES_USER"`
|
||||
|
||||
create schema if not exists _realtime;
|
||||
alter schema _realtime owner to :pguser;
|
@ -1,8 +0,0 @@
|
||||
-- NOTE: change to your own passwords for production environments
|
||||
\set pgpass `echo "$POSTGRES_PASSWORD"`
|
||||
|
||||
ALTER USER authenticator WITH PASSWORD :'pgpass';
|
||||
ALTER USER pgbouncer WITH PASSWORD :'pgpass';
|
||||
ALTER USER supabase_auth_admin WITH PASSWORD :'pgpass';
|
||||
ALTER USER supabase_functions_admin WITH PASSWORD :'pgpass';
|
||||
ALTER USER supabase_storage_admin WITH PASSWORD :'pgpass';
|
@ -1,208 +0,0 @@
|
||||
BEGIN;
|
||||
-- Create pg_net extension
|
||||
CREATE EXTENSION IF NOT EXISTS pg_net SCHEMA extensions;
|
||||
-- Create supabase_functions schema
|
||||
CREATE SCHEMA supabase_functions AUTHORIZATION supabase_admin;
|
||||
GRANT USAGE ON SCHEMA supabase_functions TO postgres, anon, authenticated, service_role;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON TABLES TO postgres, anon, authenticated, service_role;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON FUNCTIONS TO postgres, anon, authenticated, service_role;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON SEQUENCES TO postgres, anon, authenticated, service_role;
|
||||
-- supabase_functions.migrations definition
|
||||
CREATE TABLE supabase_functions.migrations (
|
||||
version text PRIMARY KEY,
|
||||
inserted_at timestamptz NOT NULL DEFAULT NOW()
|
||||
);
|
||||
-- Initial supabase_functions migration
|
||||
INSERT INTO supabase_functions.migrations (version) VALUES ('initial');
|
||||
-- supabase_functions.hooks definition
|
||||
CREATE TABLE supabase_functions.hooks (
|
||||
id bigserial PRIMARY KEY,
|
||||
hook_table_id integer NOT NULL,
|
||||
hook_name text NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
request_id bigint
|
||||
);
|
||||
CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functions.hooks USING btree (request_id);
|
||||
CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_functions.hooks USING btree (hook_table_id, hook_name);
|
||||
COMMENT ON TABLE supabase_functions.hooks IS 'Supabase Functions Hooks: Audit trail for triggered hooks.';
|
||||
CREATE FUNCTION supabase_functions.http_request()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $function$
|
||||
DECLARE
|
||||
request_id bigint;
|
||||
payload jsonb;
|
||||
url text := TG_ARGV[0]::text;
|
||||
method text := TG_ARGV[1]::text;
|
||||
headers jsonb DEFAULT '{}'::jsonb;
|
||||
params jsonb DEFAULT '{}'::jsonb;
|
||||
timeout_ms integer DEFAULT 1000;
|
||||
BEGIN
|
||||
IF url IS NULL OR url = 'null' THEN
|
||||
RAISE EXCEPTION 'url argument is missing';
|
||||
END IF;
|
||||
|
||||
IF method IS NULL OR method = 'null' THEN
|
||||
RAISE EXCEPTION 'method argument is missing';
|
||||
END IF;
|
||||
|
||||
IF TG_ARGV[2] IS NULL OR TG_ARGV[2] = 'null' THEN
|
||||
headers = '{"Content-Type": "application/json"}'::jsonb;
|
||||
ELSE
|
||||
headers = TG_ARGV[2]::jsonb;
|
||||
END IF;
|
||||
|
||||
IF TG_ARGV[3] IS NULL OR TG_ARGV[3] = 'null' THEN
|
||||
params = '{}'::jsonb;
|
||||
ELSE
|
||||
params = TG_ARGV[3]::jsonb;
|
||||
END IF;
|
||||
|
||||
IF TG_ARGV[4] IS NULL OR TG_ARGV[4] = 'null' THEN
|
||||
timeout_ms = 1000;
|
||||
ELSE
|
||||
timeout_ms = TG_ARGV[4]::integer;
|
||||
END IF;
|
||||
|
||||
CASE
|
||||
WHEN method = 'GET' THEN
|
||||
SELECT http_get INTO request_id FROM net.http_get(
|
||||
url,
|
||||
params,
|
||||
headers,
|
||||
timeout_ms
|
||||
);
|
||||
WHEN method = 'POST' THEN
|
||||
payload = jsonb_build_object(
|
||||
'old_record', OLD,
|
||||
'record', NEW,
|
||||
'type', TG_OP,
|
||||
'table', TG_TABLE_NAME,
|
||||
'schema', TG_TABLE_SCHEMA
|
||||
);
|
||||
|
||||
SELECT http_post INTO request_id FROM net.http_post(
|
||||
url,
|
||||
payload,
|
||||
params,
|
||||
headers,
|
||||
timeout_ms
|
||||
);
|
||||
ELSE
|
||||
RAISE EXCEPTION 'method argument % is invalid', method;
|
||||
END CASE;
|
||||
|
||||
INSERT INTO supabase_functions.hooks
|
||||
(hook_table_id, hook_name, request_id)
|
||||
VALUES
|
||||
(TG_RELID, TG_NAME, request_id);
|
||||
|
||||
RETURN NEW;
|
||||
END
|
||||
$function$;
|
||||
-- Supabase super admin
|
||||
DO
|
||||
$$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_roles
|
||||
WHERE rolname = 'supabase_functions_admin'
|
||||
)
|
||||
THEN
|
||||
CREATE USER supabase_functions_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
GRANT ALL PRIVILEGES ON SCHEMA supabase_functions TO supabase_functions_admin;
|
||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA supabase_functions TO supabase_functions_admin;
|
||||
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA supabase_functions TO supabase_functions_admin;
|
||||
ALTER USER supabase_functions_admin SET search_path = "supabase_functions";
|
||||
ALTER table "supabase_functions".migrations OWNER TO supabase_functions_admin;
|
||||
ALTER table "supabase_functions".hooks OWNER TO supabase_functions_admin;
|
||||
ALTER function "supabase_functions".http_request() OWNER TO supabase_functions_admin;
|
||||
GRANT supabase_functions_admin TO postgres;
|
||||
-- Remove unused supabase_pg_net_admin role
|
||||
DO
|
||||
$$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_roles
|
||||
WHERE rolname = 'supabase_pg_net_admin'
|
||||
)
|
||||
THEN
|
||||
REASSIGN OWNED BY supabase_pg_net_admin TO supabase_admin;
|
||||
DROP OWNED BY supabase_pg_net_admin;
|
||||
DROP ROLE supabase_pg_net_admin;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
-- pg_net grants when extension is already enabled
|
||||
DO
|
||||
$$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_extension
|
||||
WHERE extname = 'pg_net'
|
||||
)
|
||||
THEN
|
||||
GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
|
||||
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
|
||||
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
|
||||
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
|
||||
REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
|
||||
REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||
GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
-- Event trigger for pg_net
|
||||
CREATE OR REPLACE FUNCTION extensions.grant_pg_net_access()
|
||||
RETURNS event_trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_event_trigger_ddl_commands() AS ev
|
||||
JOIN pg_extension AS ext
|
||||
ON ev.objid = ext.oid
|
||||
WHERE ext.extname = 'pg_net'
|
||||
)
|
||||
THEN
|
||||
GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
|
||||
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
|
||||
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
|
||||
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
|
||||
REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
|
||||
REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||
GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
COMMENT ON FUNCTION extensions.grant_pg_net_access IS 'Grants access to pg_net';
|
||||
DO
|
||||
$$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_event_trigger
|
||||
WHERE evtname = 'issue_pg_net_access'
|
||||
) THEN
|
||||
CREATE EVENT TRIGGER issue_pg_net_access ON ddl_command_end WHEN TAG IN ('CREATE EXTENSION')
|
||||
EXECUTE PROCEDURE extensions.grant_pg_net_access();
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
INSERT INTO supabase_functions.migrations (version) VALUES ('20210809183423_update_grants');
|
||||
ALTER function supabase_functions.http_request() SECURITY DEFINER;
|
||||
ALTER function supabase_functions.http_request() SET search_path = supabase_functions;
|
||||
REVOKE ALL ON FUNCTION supabase_functions.http_request() FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION supabase_functions.http_request() TO postgres, anon, authenticated, service_role;
|
||||
COMMIT;
|
@ -1,16 +0,0 @@
|
||||
// Follow this setup guide to integrate the Deno language server with your editor:
|
||||
// https://deno.land/manual/getting_started/setup_your_environment
|
||||
// This enables autocomplete, go to definition, etc.
|
||||
|
||||
import { serve } from "https://deno.land/std@0.177.1/http/server.ts"
|
||||
|
||||
serve(async () => {
|
||||
return new Response(
|
||||
`"Hello from Edge Functions!"`,
|
||||
{ headers: { "Content-Type": "application/json" } },
|
||||
)
|
||||
})
|
||||
|
||||
// To invoke:
|
||||
// curl 'http://localhost:<KONG_HTTP_PORT>/functions/v1/hello' \
|
||||
// --header 'Authorization: Bearer <anon/service_role API key>'
|
@ -1,94 +0,0 @@
|
||||
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
|
||||
import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts'
|
||||
|
||||
console.log('main function started')
|
||||
|
||||
const JWT_SECRET = Deno.env.get('JWT_SECRET')
|
||||
const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'
|
||||
|
||||
function getAuthToken(req: Request) {
|
||||
const authHeader = req.headers.get('authorization')
|
||||
if (!authHeader) {
|
||||
throw new Error('Missing authorization header')
|
||||
}
|
||||
const [bearer, token] = authHeader.split(' ')
|
||||
if (bearer !== 'Bearer') {
|
||||
throw new Error(`Auth header is not 'Bearer {token}'`)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
async function verifyJWT(jwt: string): Promise<boolean> {
|
||||
const encoder = new TextEncoder()
|
||||
const secretKey = encoder.encode(JWT_SECRET)
|
||||
try {
|
||||
await jose.jwtVerify(jwt, secretKey)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
serve(async (req: Request) => {
|
||||
if (req.method !== 'OPTIONS' && VERIFY_JWT) {
|
||||
try {
|
||||
const token = getAuthToken(req)
|
||||
const isValidJWT = await verifyJWT(token)
|
||||
|
||||
if (!isValidJWT) {
|
||||
return new Response(JSON.stringify({ msg: 'Invalid JWT' }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return new Response(JSON.stringify({ msg: e.toString() }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const url = new URL(req.url)
|
||||
const { pathname } = url
|
||||
const path_parts = pathname.split('/')
|
||||
const service_name = path_parts[1]
|
||||
|
||||
if (!service_name || service_name === '') {
|
||||
const error = { msg: 'missing function name in request' }
|
||||
return new Response(JSON.stringify(error), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
|
||||
const servicePath = `/home/deno/functions/${service_name}`
|
||||
console.error(`serving the request with ${servicePath}`)
|
||||
|
||||
const memoryLimitMb = 150
|
||||
const workerTimeoutMs = 1 * 60 * 1000
|
||||
const noModuleCache = false
|
||||
const importMapPath = null
|
||||
const envVarsObj = Deno.env.toObject()
|
||||
const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]])
|
||||
|
||||
try {
|
||||
const worker = await EdgeRuntime.userWorkers.create({
|
||||
servicePath,
|
||||
memoryLimitMb,
|
||||
workerTimeoutMs,
|
||||
noModuleCache,
|
||||
importMapPath,
|
||||
envVars,
|
||||
})
|
||||
return await worker.fetch(req)
|
||||
} catch (e) {
|
||||
const error = { msg: e.toString() }
|
||||
return new Response(JSON.stringify(error), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
})
|
@ -1,232 +0,0 @@
|
||||
api:
|
||||
enabled: true
|
||||
address: 0.0.0.0:9001
|
||||
|
||||
sources:
|
||||
docker_host:
|
||||
type: docker_logs
|
||||
exclude_containers:
|
||||
- supabase-vector
|
||||
|
||||
transforms:
|
||||
project_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- docker_host
|
||||
source: |-
|
||||
.project = "default"
|
||||
.event_message = del(.message)
|
||||
.appname = del(.container_name)
|
||||
del(.container_created_at)
|
||||
del(.container_id)
|
||||
del(.source_type)
|
||||
del(.stream)
|
||||
del(.label)
|
||||
del(.image)
|
||||
del(.host)
|
||||
del(.stream)
|
||||
router:
|
||||
type: route
|
||||
inputs:
|
||||
- project_logs
|
||||
route:
|
||||
kong: '.appname == "supabase-kong"'
|
||||
auth: '.appname == "supabase-auth"'
|
||||
rest: '.appname == "supabase-rest"'
|
||||
realtime: '.appname == "supabase-realtime"'
|
||||
storage: '.appname == "supabase-storage"'
|
||||
functions: '.appname == "supabase-functions"'
|
||||
db: '.appname == "supabase-db"'
|
||||
# Ignores non nginx errors since they are related with kong booting up
|
||||
kong_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.kong
|
||||
source: |-
|
||||
req, err = parse_nginx_log(.event_message, "combined")
|
||||
if err == null {
|
||||
.timestamp = req.timestamp
|
||||
.metadata.request.headers.referer = req.referer
|
||||
.metadata.request.headers.user_agent = req.agent
|
||||
.metadata.request.headers.cf_connecting_ip = req.client
|
||||
.metadata.request.method = req.method
|
||||
.metadata.request.path = req.path
|
||||
.metadata.request.protocol = req.protocol
|
||||
.metadata.response.status_code = req.status
|
||||
}
|
||||
if err != null {
|
||||
abort
|
||||
}
|
||||
# Ignores non nginx errors since they are related with kong booting up
|
||||
kong_err:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.kong
|
||||
source: |-
|
||||
.metadata.request.method = "GET"
|
||||
.metadata.response.status_code = 200
|
||||
parsed, err = parse_nginx_log(.event_message, "error")
|
||||
if err == null {
|
||||
.timestamp = parsed.timestamp
|
||||
.severity = parsed.severity
|
||||
.metadata.request.host = parsed.host
|
||||
.metadata.request.headers.cf_connecting_ip = parsed.client
|
||||
url, err = split(parsed.request, " ")
|
||||
if err == null {
|
||||
.metadata.request.method = url[0]
|
||||
.metadata.request.path = url[1]
|
||||
.metadata.request.protocol = url[2]
|
||||
}
|
||||
}
|
||||
if err != null {
|
||||
abort
|
||||
}
|
||||
# Gotrue logs are structured json strings which frontend parses directly. But we keep metadata for consistency.
|
||||
auth_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.auth
|
||||
source: |-
|
||||
parsed, err = parse_json(.event_message)
|
||||
if err == null {
|
||||
.metadata.timestamp = parsed.time
|
||||
.metadata = merge!(.metadata, parsed)
|
||||
}
|
||||
# PostgREST logs are structured so we separate timestamp from message using regex
|
||||
rest_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.rest
|
||||
source: |-
|
||||
parsed, err = parse_regex(.event_message, r'^(?P<time>.*): (?P<msg>.*)$')
|
||||
if err == null {
|
||||
.event_message = parsed.msg
|
||||
.timestamp = to_timestamp!(parsed.time)
|
||||
.metadata.host = .project
|
||||
}
|
||||
# Realtime logs are structured so we parse the severity level using regex (ignore time because it has no date)
|
||||
realtime_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.realtime
|
||||
source: |-
|
||||
.metadata.project = del(.project)
|
||||
.metadata.external_id = .metadata.project
|
||||
parsed, err = parse_regex(.event_message, r'^(?P<time>\d+:\d+:\d+\.\d+) \[(?P<level>\w+)\] (?P<msg>.*)$')
|
||||
if err == null {
|
||||
.event_message = parsed.msg
|
||||
.metadata.level = parsed.level
|
||||
}
|
||||
# Storage logs may contain json objects so we parse them for completeness
|
||||
storage_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.storage
|
||||
source: |-
|
||||
.metadata.project = del(.project)
|
||||
.metadata.tenantId = .metadata.project
|
||||
parsed, err = parse_json(.event_message)
|
||||
if err == null {
|
||||
.event_message = parsed.msg
|
||||
.metadata.level = parsed.level
|
||||
.metadata.timestamp = parsed.time
|
||||
.metadata.context[0].host = parsed.hostname
|
||||
.metadata.context[0].pid = parsed.pid
|
||||
}
|
||||
# Postgres logs some messages to stderr which we map to warning severity level
|
||||
db_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.db
|
||||
source: |-
|
||||
.metadata.host = "db-default"
|
||||
.metadata.parsed.timestamp = .timestamp
|
||||
|
||||
parsed, err = parse_regex(.event_message, r'.*(?P<level>INFO|NOTICE|WARNING|ERROR|LOG|FATAL|PANIC?):.*', numeric_groups: true)
|
||||
|
||||
if err != null || parsed == null {
|
||||
.metadata.parsed.error_severity = "info"
|
||||
}
|
||||
if parsed != null {
|
||||
.metadata.parsed.error_severity = parsed.level
|
||||
}
|
||||
if .metadata.parsed.error_severity == "info" {
|
||||
.metadata.parsed.error_severity = "log"
|
||||
}
|
||||
.metadata.parsed.error_severity = upcase!(.metadata.parsed.error_severity)
|
||||
|
||||
sinks:
|
||||
logflare_auth:
|
||||
type: 'http'
|
||||
inputs:
|
||||
- auth_logs
|
||||
encoding:
|
||||
codec: 'json'
|
||||
method: 'post'
|
||||
request:
|
||||
retry_max_duration_secs: 10
|
||||
uri: 'http://analytics:4000/api/logs?source_name=gotrue.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
|
||||
logflare_realtime:
|
||||
type: 'http'
|
||||
inputs:
|
||||
- realtime_logs
|
||||
encoding:
|
||||
codec: 'json'
|
||||
method: 'post'
|
||||
request:
|
||||
retry_max_duration_secs: 10
|
||||
uri: 'http://analytics:4000/api/logs?source_name=realtime.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
|
||||
logflare_rest:
|
||||
type: 'http'
|
||||
inputs:
|
||||
- rest_logs
|
||||
encoding:
|
||||
codec: 'json'
|
||||
method: 'post'
|
||||
request:
|
||||
retry_max_duration_secs: 10
|
||||
uri: 'http://analytics:4000/api/logs?source_name=postgREST.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
|
||||
logflare_db:
|
||||
type: 'http'
|
||||
inputs:
|
||||
- db_logs
|
||||
encoding:
|
||||
codec: 'json'
|
||||
method: 'post'
|
||||
request:
|
||||
retry_max_duration_secs: 10
|
||||
# We must route the sink through kong because ingesting logs before logflare is fully initialised will
|
||||
# lead to broken queries from studio. This works by the assumption that containers are started in the
|
||||
# following order: vector > db > logflare > kong
|
||||
uri: 'http://kong:8000/analytics/v1/api/logs?source_name=postgres.logs&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
|
||||
logflare_functions:
|
||||
type: 'http'
|
||||
inputs:
|
||||
- router.functions
|
||||
encoding:
|
||||
codec: 'json'
|
||||
method: 'post'
|
||||
request:
|
||||
retry_max_duration_secs: 10
|
||||
uri: 'http://analytics:4000/api/logs?source_name=deno-relay-logs&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
|
||||
logflare_storage:
|
||||
type: 'http'
|
||||
inputs:
|
||||
- storage_logs
|
||||
encoding:
|
||||
codec: 'json'
|
||||
method: 'post'
|
||||
request:
|
||||
retry_max_duration_secs: 10
|
||||
uri: 'http://analytics:4000/api/logs?source_name=storage.logs.prod.2&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
|
||||
logflare_kong:
|
||||
type: 'http'
|
||||
inputs:
|
||||
- kong_logs
|
||||
- kong_err
|
||||
encoding:
|
||||
codec: 'json'
|
||||
method: 'post'
|
||||
request:
|
||||
retry_max_duration_secs: 10
|
||||
uri: 'http://analytics:4000/api/logs?source_name=cloudflare.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
|
Before Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 446 KiB |
Before Width: | Height: | Size: 319 KiB |
Before Width: | Height: | Size: 689 KiB |
Before Width: | Height: | Size: 153 KiB |
Before Width: | Height: | Size: 539 KiB |
Before Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 446 KiB |
Before Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 539 KiB |
Before Width: | Height: | Size: 689 KiB |
Before Width: | Height: | Size: 153 KiB |
@ -3,6 +3,8 @@ module.exports = {
|
||||
content: [
|
||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/**/*.{js,jsx,ts,tsx}",
|
||||
"./node_modules/react-tailwindcss-select/dist/index.esm.js"
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
@ -14,31 +16,27 @@ module.exports = {
|
||||
},
|
||||
aspectRatio: {}, // enable aspect-ratio plugin
|
||||
textShadow: {
|
||||
'pink-glow': '0 0 4px #524FFD, 0 0 4px #524FFD, 0 0 4px #524FFD, 0 0 4px #524FFD',
|
||||
'purple-grey-glow': '0 0 4px #9f7aea, 0 0 15px #9f7aea, 0 0 20px #9f7aea, 0 0 25px #9f7aea',
|
||||
'green-grey-glow': '0 0 4px #32CD32, 0 0 15px #32CD32, 0 0 20px #32CD32, 0 0 25px #32CD32',
|
||||
// 'pink-glow': '0 0 4px #524FFD, 0 0 4px #524FFD, 0 0 4px #524FFD, 0 0 4px #524FFD',
|
||||
},
|
||||
colors: {
|
||||
|
||||
"neroshi-blue": {
|
||||
50: "#EBEBFF",
|
||||
100: "#D2D2FE",
|
||||
200: "#A6A4FE",
|
||||
300: "#7E7CFD",
|
||||
400: "#524FFD",
|
||||
500: "#2522FC",
|
||||
600: "#0703E2",
|
||||
700: "#0502AB",
|
||||
800: "#03026F",
|
||||
900: "#020137",
|
||||
950: "#01001E"
|
||||
},
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
btn: {
|
||||
background: "hsl(var(--btn-background))",
|
||||
"background-hover": "hsl(var(--btn-background-hover))",
|
||||
},
|
||||
'primary': 'var(--color-primary)',
|
||||
'primary-light': 'var(--color-primary-light)',
|
||||
'primary-dark': 'var(--color-primary-dark)',
|
||||
'secondary': 'var(--color-secondary)',
|
||||
'secondary-light': 'var(--color-secondary-light)',
|
||||
'secondary-dark': 'var(--color-secondary-dark)',
|
||||
'error': 'var(--color-error)',
|
||||
'error-light': 'var(--color-error-light)',
|
||||
'error-dark': 'var(--color-error-dark)',
|
||||
'success': 'var(--color-success)',
|
||||
'success-light': 'var(--color-success-light)',
|
||||
'success-dark': 'var(--color-success-dark)',
|
||||
'warning': 'var(--color-warning)',
|
||||
'warning-light': 'var(--color-warning-light)',
|
||||
'warning-dark': 'var(--color-warning-dark)',
|
||||
'info': 'var(--color-info)',
|
||||
'info-light': 'var(--color-info-light)',
|
||||
'info-dark': 'var(--color-info-dark)',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -24,5 +24,5 @@
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"exclude": ["node_modules","supabase/volumes/functions/**/*"]
|
||||
}
|
||||
|
3
types/react-easy-panzoom.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// types/react-easy-panzoom.d.ts
|
||||
|
||||
declare module 'react-easy-panzoom';
|