Compare commits

...

227 Commits
0.1.8 ... main

Author SHA1 Message Date
2e19d5fdeb Update .env 2024-06-08 20:44:20 -04:00
63f31e0f4c Update server.ts 2024-06-08 20:08:25 -04:00
f75d889ee8 Delete page.tsx 2024-06-08 19:51:44 -04:00
8877362484 Update server.ts 2024-06-08 19:50:08 -04:00
082c161959 Update page.tsx 2024-06-08 19:45:31 -04:00
5d96002a2e fix 2024-06-08 19:43:26 -04:00
65ddd704f0 fix 2024-06-08 19:34:52 -04:00
b12d095e3e Update page.tsx 2024-06-08 19:15:21 -04:00
581ceb028a fix 2024-06-08 19:14:31 -04:00
7ac84fc0cf Update page.tsx 2024-06-08 19:13:54 -04:00
164aec1c51 fix: hooked up tiers to galleries page 2024-06-08 19:11:32 -04:00
420e029d66 fix: gallery admin pages 2024-06-08 19:01:55 -04:00
d94f3b825b fix: gallery admin page 2024-06-08 18:53:10 -04:00
7635178e3c fix: gallery pages 2024-06-08 18:45:28 -04:00
a9968ccb03 fix: added ui for managing theme and tiers 2024-06-08 00:26:23 -04:00
4d86b8aeed Update page.tsx 2024-06-04 23:07:20 -04:00
19bee6998a fix: added tier pages 2024-06-04 22:59:02 -04:00
fcf83c6435 Merge branch 'main' of https://github.com/D4M13N-D3V/neroshitron 2024-06-04 00:27:25 -04:00
32ba288463 Update diagrams.drawio 2024-06-04 00:27:23 -04:00
334db37d18
Update README.md 2024-06-04 00:25:36 -04:00
1112191b61
Update README.md 2024-06-04 00:24:08 -04:00
52b7771994
Update README.md 2024-06-04 00:23:21 -04:00
9e76f9315b
Update README.md 2024-06-03 23:50:58 -04:00
c9bf8e8431 Update navigation_bar.tsx 2024-06-03 22:25:21 -04:00
691292becc fix:reactive pages 2024-06-03 22:21:19 -04:00
e8fa3d2ce0 Update page.tsx 2024-06-03 21:45:50 -04:00
fd76220269 fix: adding duplicates 2024-06-03 21:37:05 -04:00
e9e4f25520 Update search_input.tsx 2024-06-03 21:24:42 -04:00
234cc989f1 fixed: nsfw button 2024-06-03 21:23:08 -04:00
e6a81a3bdf fix: tag format 2024-06-03 21:02:48 -04:00
96aeca6242 Update page.tsx 2024-06-03 02:39:11 -04:00
a6d050c35f Update page.tsx 2024-06-03 02:38:20 -04:00
f790a03c27 fix 2024-06-03 02:29:09 -04:00
6f5bf90a0b fix 2024-06-03 02:25:25 -04:00
e5daff376a Update search.tsx 2024-06-03 02:15:13 -04:00
a0a38389a6 Update page.tsx 2024-06-03 02:14:45 -04:00
85c066baec Update page.tsx 2024-06-03 02:13:38 -04:00
9c6bdddd22 Update page.tsx 2024-06-03 02:12:25 -04:00
87ba2e032d fix 2024-06-03 02:10:39 -04:00
28414630a4 feat: gallery management pages 2024-06-03 02:00:05 -04:00
534cbf2957 fix 2024-06-02 23:37:41 -04:00
481b4f698c fix: 2024-06-02 23:14:30 -04:00
172a3015af Merge branch 'main' of https://github.com/D4M13N-D3V/neroshitron 2024-06-02 22:58:53 -04:00
6564be1a60 Update search_input.tsx 2024-06-02 22:58:49 -04:00
fdcaccfbd6
23 replace recenter with back (#37)
* fix:23

* fix

* fix: girl bg

* Update search_input.tsx

* Update search_input.tsx
2024-06-02 22:57:20 -04:00
6541bd94fe
23 replace recenter with back (#36)
* fix:23

* fix

* fix: girl bg

* Update search_input.tsx
2024-06-02 22:52:44 -04:00
82fdd61170
fix:23 (#35) 2024-06-02 20:25:29 -04:00
caca715cc0
fix:22 (#34) 2024-06-02 20:21:12 -04:00
74335ff6cf Update seed.sql 2024-06-02 19:51:45 -04:00
3206a19f7a Delete route.ts 2024-06-02 19:31:51 -04:00
e48d3ebafb fix;admin 2024-06-02 19:26:12 -04:00
168c835a23 fix: animations 2024-06-02 17:58:27 -04:00
0127d00021 fix: locked down navbar options for admin pages and added rls policies 2024-06-02 17:50:08 -04:00
02872252b5 feat: tags admin ui 2024-06-02 17:35:10 -04:00
b6736651c5 fix 2024-06-02 07:54:45 -04:00
78a25ab398 Update right_hand_layout_image.tsx 2024-06-02 07:51:11 -04:00
58d384a3fc fix: shadows 2024-06-02 07:49:41 -04:00
76a09c8e8c fix: nav 2024-06-02 07:43:33 -04:00
73836c5893 fix: styling for tags search 2024-06-02 07:17:14 -04:00
4c972778cd fix: moved anime girl to layout 2024-06-02 06:12:04 -04:00
795fc0f5f7 fix
const [scrollPosition, setScrollPosition] = useState(0);
  const [color, setColor] = useState('black');

  useEffect(() => {
  }, []);
2024-06-02 05:57:18 -04:00
e4dde90da4 fix 2024-06-02 04:56:09 -04:00
fa6bf10249 Update page.tsx 2024-06-02 04:52:32 -04:00
cd5d0bf304 Update page.tsx 2024-06-02 04:50:23 -04:00
4a3c7023ba Update page.tsx 2024-06-02 04:46:42 -04:00
ea3a14a6e4 fix; delete button 2024-06-02 04:46:21 -04:00
451bdd1106 Update page.tsx 2024-06-02 04:42:34 -04:00
2b21c2feff Update page.tsx 2024-06-02 04:37:59 -04:00
02e85c54fc Update page.tsx 2024-06-02 04:35:15 -04:00
ac4007a82e Update page.tsx 2024-06-02 04:31:38 -04:00
e584a43145 Update navigation_bar.tsx 2024-06-02 04:28:52 -04:00
b232be9377 fix: tags 2024-06-02 04:25:36 -04:00
46a20bbd7e Update page.tsx 2024-06-02 03:52:45 -04:00
894fb58c29 fix: create page 2024-06-02 03:51:53 -04:00
2c59660566 feat: create page 2024-06-02 03:48:21 -04:00
666e377cdb Update search_input.tsx 2024-06-02 03:17:05 -04:00
da6fc56b86 fix 2024-06-02 03:15:59 -04:00
5daa585a9e Merge branch 'main' of https://github.com/D4M13N-D3V/neroshitron 2024-06-02 03:09:02 -04:00
d33f36adaf fix 2024-06-02 03:08:55 -04:00
e03816928a
fix:images in gallery (#18) 2024-06-02 02:49:55 -04:00
de08c2ddd0 Update navigation_bar.tsx 2024-06-02 02:20:37 -04:00
9de286f920 fix: added a thing telling user where tags are 2024-06-02 02:13:36 -04:00
45d3b459d8 Update search_input.tsx 2024-06-02 02:11:56 -04:00
712e1ce5cd Update search_input.tsx 2024-06-02 02:07:20 -04:00
912fa157e2 Update search_input.tsx 2024-06-02 02:04:58 -04:00
394163198c Update gallery.tsx 2024-06-02 01:53:31 -04:00
f74d7a8fa9 Update search_input.tsx 2024-06-02 01:49:24 -04:00
cfe47be428 fix: neroshi tag isnt pushed 2024-06-02 01:37:52 -04:00
bd334bcbea fix: first tag 2024-06-02 01:37:03 -04:00
54ec36cebc fix: nsfw button 2024-06-02 01:32:37 -04:00
f175b31976 fix: css themiing 2024-06-02 01:24:14 -04:00
9b5c3d44c5 chore: formatting 2024-06-02 00:36:02 -04:00
97f2a31951 Update gallery_thumbnail.tsx 2024-06-02 00:31:19 -04:00
4c564df94a Update search_input.tsx 2024-06-02 00:25:42 -04:00
2edf752b05 Update search_input.tsx 2024-06-02 00:20:03 -04:00
d7f3330dc2 Merge branch 'main' of https://github.com/D4M13N-D3V/neroshitron 2024-06-02 00:19:43 -04:00
afe4076874 Update search_input.tsx 2024-06-02 00:19:36 -04:00
f86c618b1d
fix: remove scrollbar (#8)
* Update .gitignore

* fix: added a badge for number of tags selected

* Update tag_selector.tsx

* tag search

* fix: search styling
2024-06-02 00:15:21 -04:00
7fec3450d9
fix:add badge showing how many tags selected (#7)
* Update .gitignore

* fix: added a badge for number of tags selected
2024-06-01 16:36:06 -04:00
41c90c2a58
fix: clicking tag breaks tag ui (#6) 2024-06-01 16:30:05 -04:00
bef53cadce
fix: gallery not opening (#5) 2024-06-01 16:29:58 -04:00
f30d2c5a2a
feat: new shit (#4)
* fix: navigation bar code is minimized, started seperating out components and refactoring them

* feat: search components

* Update search.tsx

* fix: autofocus

* fix: tags and search

* Update page_old.tsx

* Update galleries.tsx

* Update tag_selector.tsx

* Update tag_selector.tsx
2024-06-01 06:39:05 -04:00
f2bd76487e
feat: tags
* fix: navigation bar code is minimized, started seperating out components and refactoring them

* feat: search components

* Update search.tsx

* fix: autofocus

* fix: tags and search
2024-06-01 06:21:59 -04:00
92c2317af1 Update NavigationBar.tsx 2024-05-28 00:36:27 -04:00
0b390b7658 Update NavigationBar.tsx 2024-05-28 00:34:14 -04:00
8a994123af Update _middleware.tsx 2024-05-28 00:28:28 -04:00
dcfe7c5676 fix:header 2024-05-28 00:28:03 -04:00
21fa376190 fix: navbar highlight 2024-05-28 00:20:42 -04:00
23137d4e04 Update NavigationBar.tsx 2024-05-28 00:11:45 -04:00
b99f3fa256 Update NavigationBar.tsx 2024-05-28 00:10:45 -04:00
2c036794ff Update NavigationBar.tsx 2024-05-28 00:09:23 -04:00
ee1272b520 Update NavigationBar.tsx 2024-05-28 00:08:42 -04:00
2cccc65a9a Update NavigationBar.tsx 2024-05-28 00:08:32 -04:00
e17aba510c Update NavigationBar.tsx 2024-05-28 00:07:38 -04:00
381009d5c9 chore :analytics 2024-05-28 00:04:45 -04:00
401e3d41dc Update NavigationBar.tsx 2024-05-28 00:02:55 -04:00
82728cbc97 Update NavigationBar.tsx 2024-05-27 23:58:58 -04:00
de0a5a8e73 Update NavigationBar.tsx 2024-05-27 23:55:21 -04:00
057c20cd21 Update NavigationBar.tsx 2024-05-27 23:54:39 -04:00
b2f700280a chore: installed speed insights 2024-05-27 23:47:49 -04:00
e8161fc226 Update NavigationBar.tsx 2024-05-27 23:46:24 -04:00
10f8e5d289 Update NavigationBar.tsx 2024-05-27 23:28:16 -04:00
54a771b47f Update page.tsx 2024-05-27 23:26:41 -04:00
e82305954e Update page.tsx 2024-05-27 23:24:59 -04:00
fe09172881 Update page.tsx 2024-05-27 23:24:18 -04:00
caac354273 Update page.tsx 2024-05-27 23:19:30 -04:00
ea21548c68 Update layout.tsx 2024-05-27 23:16:08 -04:00
eb8df7bed7 Update NavigationBar.tsx 2024-05-27 21:45:17 -04:00
4b0060e5dd cleanup 2024-05-27 21:38:21 -04:00
08ce73e64e chrore ; update 2024-05-27 20:53:24 -04:00
af0dd625b4 feat: nsfw toggle 2024-05-27 20:40:23 -04:00
43432a8bcd chore: more feel on gallery tiles 2024-05-27 20:19:20 -04:00
5e5678c280 fix: animate the search bar coming out 2024-05-27 20:10:03 -04:00
c0a382e4a7 Merge branch 'main' of https://github.com/D4M13N-D3V/neroshitron 2024-05-27 20:04:09 -04:00
ab59264d84 chore:diagrams 2024-05-27 20:04:05 -04:00
ea68c28b2e
Update README.md 2024-05-27 20:02:22 -04:00
6cd71b64e8 fix:bluring 2024-05-27 17:42:06 -04:00
b93f3945bf fix; skeleton for tags/search 2024-05-27 17:37:19 -04:00
53fcbb76f6 chore:seed data 2024-05-27 17:37:02 -04:00
8ad20079de fix: not being able to scroll to bottom 2024-05-27 17:18:44 -04:00
10f36e54ff feat: image skeletons 2024-05-27 17:09:49 -04:00
e930b2ce73 fix: resizing for gallery 2024-05-27 17:02:25 -04:00
b5a796d3ff fix:place holder for search 2024-05-27 17:02:17 -04:00
abab8c9336 fix:navbar icons 2024-05-27 16:45:37 -04:00
82399d1bc3 fix:gallery page resizing 2024-05-27 16:38:34 -04:00
cb37b333b0 Update route.ts 2024-05-27 11:40:00 -04:00
adfab3585b fix :nsfw not bluring 2024-05-27 11:38:38 -04:00
052bc21604 fix: wrong pic count 2024-05-27 11:36:56 -04:00
1647474a74 Update README.md 2024-05-27 11:29:29 -04:00
cf7e485f4e Update seed.sql 2024-05-27 11:28:10 -04:00
ab0198df01 Update seed.sql 2024-05-27 11:18:03 -04:00
a48488a128 fix:supabase+tags+changed data models+seeded data 2024-05-27 11:17:56 -04:00
82efd1f10e Update README.md 2024-05-27 09:53:06 -04:00
70ea1d8a9c chore :readme default env 2024-05-27 09:52:26 -04:00
97eba7457d Update README.md 2024-05-27 09:42:06 -04:00
8eedebfee2 Update README.md 2024-05-27 09:41:33 -04:00
9f205dd034 feat:setup supabase cli 2024-05-27 09:40:21 -04:00
f7cef69b5b fix: tiles no longer get cut off 2024-05-27 08:19:53 -04:00
090630c507 feat: blurs based on tier access 2024-05-27 00:14:31 -04:00
6af5a0ee5d fix: icons not updating on search 2024-05-26 22:09:05 -04:00
323ba05fda fix:recenter 2024-05-26 21:57:10 -04:00
8f41b42cf4 Update gallery.tsx 2024-05-26 21:27:26 -04:00
58a10bcc41 Update gallery.tsx 2024-05-26 21:26:24 -04:00
b1bd8ab67e fix:center iamge 2024-05-26 21:16:47 -04:00
f53ea97c3e Update page.tsx 2024-05-26 21:12:39 -04:00
21307711e6 Merge branch 'main' of https://github.com/D4M13N-D3V/neroshitron 2024-05-26 21:12:10 -04:00
7780a3a0d1 Update page.tsx 2024-05-26 21:12:09 -04:00
7bf385f8c0
Update README.md 2024-05-26 20:23:58 -04:00
867af6ac1d Update README.md 2024-05-26 20:22:11 -04:00
0203f848b1 Update .gitignore 2024-05-26 20:21:42 -04:00
02f95efe7b fix: everything type safe 2024-05-26 20:20:47 -04:00
4fcb80fd75 Update page.tsx 2024-05-26 20:08:52 -04:00
eafa726b43 Merge branch 'main' of https://github.com/D4M13N-D3V/neroshitron 2024-05-26 20:06:21 -04:00
50fcd06c17 fix:layers 2024-05-26 20:06:17 -04:00
ef5f02176e
Update README.md 2024-05-26 20:01:51 -04:00
b12d3e1752 feat:search + tags 2024-05-26 19:49:20 -04:00
812c01e518 chore:readme 2024-05-26 18:21:18 -04:00
4c16ebd1d7 fix:tooltips and badges and titles on thumbnails 2024-05-26 18:01:53 -04:00
246eb848f8 fix: extra room under tiles 2024-05-26 17:17:30 -04:00
8cf5e0b827 fix: clicking opened image to close 2024-05-26 16:59:55 -04:00
1286003442 fix:added ability to configure rows 2024-05-26 16:58:48 -04:00
3291d70361 fix:redirect to gallery if already logged in 2024-05-26 16:47:32 -04:00
b14c9d54ad fix: ability to go back using escape 2024-05-26 16:45:17 -04:00
d8e30415cb fix:opening wrong and downloading wrong image 2024-05-26 16:38:35 -04:00
a3382a6434 fix:Gallery 2024-05-26 16:36:45 -04:00
396bb68b14 fix:close button 2024-05-26 14:36:20 -04:00
138f34169e stopped tracking storage 2024-05-26 14:10:40 -04:00
e6fd4cf83b Merge branch 'main' of https://github.com/D4M13N-D3V/neroshitron 2024-05-26 14:09:31 -04:00
b8146d70a0 cleanup 2024-05-26 14:09:30 -04:00
884ea73565
Delete supabase/volumes/db/data directory 2024-05-26 13:34:43 -04:00
ca13aa4ae9 CHORE:CLEANUP 2024-05-26 13:31:18 -04:00
7a568aa8d0 Update docker-compose.yml 2024-05-26 13:17:04 -04:00
0b4dc1a1e3 Update README.md 2024-05-26 13:10:45 -04:00
72a7a12cce Update README.md 2024-05-26 13:10:20 -04:00
c9e044baaa chore:readme 2024-05-26 13:08:59 -04:00
5b06ac56ff update 2024-05-26 04:41:17 -04:00
9b23070399 chore:db 2024-05-26 03:47:18 -04:00
8579f80012 Create GitVersion.yml 2024-05-26 03:47:09 -04:00
30db6f1275 feat:gallery 2024-05-26 03:46:00 -04:00
1b5e1da64d feat:gallery 2024-05-26 02:14:59 -04:00
91ea64d20f chore:volumes 2024-05-24 21:10:42 -04:00
a347dc9593 Update README.md 2024-05-24 20:53:15 -04:00
77bfdc0b0b feat: gallery page 2024-05-24 20:52:39 -04:00
ea8a876b6f chore:gallery,comissions,subscription pages, + navbar 2024-05-24 19:30:37 -04:00
f6871f4934 fix 2024-05-24 19:03:20 -04:00
cc194b239b feat:authentication 2024-05-24 17:57:03 -04:00
fac7ebd382 Update Tutorials n Documentation.md 2024-05-24 00:52:23 -04:00
6cdc511b38 Merge branch 'main' of https://github.com/D4M13N-D3V/neroshitron 2024-05-24 00:51:31 -04:00
4c402955fe chore:readme 2024-05-24 00:51:30 -04:00
0099cced35
Update README.md 2024-05-24 00:23:05 -04:00
a3de797bcb
Update README.md 2024-05-24 00:22:51 -04:00
d2c314687a
Update README.md 2024-05-24 00:22:20 -04:00
92823e9d7f
Update README.md 2024-05-24 00:22:11 -04:00
161b6d8247
Update README.md 2024-05-24 00:20:39 -04:00
33dd9d8062
Update README.md 2024-05-24 00:18:52 -04:00
7b29c0f8fb
Update README.md 2024-05-24 00:18:08 -04:00
63ceb7a8ae
Update README.md 2024-05-24 00:17:36 -04:00
3039d4eca4 chore:switched. to supabase 2024-05-24 00:16:02 -04:00
9bd8962ea4 Merge branch 'main' of https://github.com/D4M13N-D3V/neroshitron 2024-05-23 21:08:48 -04:00
663e0c8421 chore:changed to js from ts 2024-05-23 21:08:44 -04:00
14ea434a23
Update README.md 2024-05-23 20:59:21 -04:00
a4dbe3e624
Update README.md 2024-05-23 20:58:51 -04:00
5c4a48ad11
Update README.md 2024-05-23 20:58:24 -04:00
f804bda157
Update README.md 2024-05-23 20:58:04 -04:00
c45f89d4a8
Update README.md 2024-05-23 20:57:10 -04:00
1df09fa2c4
Update README.md 2024-05-23 14:59:00 -04:00
c58a57c31a fix:remove tamagui 2024-05-23 14:32:21 -04:00
99 changed files with 8447 additions and 150635 deletions

2
.default.env Normal file
View File

@ -0,0 +1,2 @@
NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE

2
.env Normal file
View File

@ -0,0 +1,2 @@
NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0

4
.env.example Normal file
View File

@ -0,0 +1,4 @@
# Update these with your Supabase details from your project settings > API
# https://app.supabase.com/project/_/settings/api
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

2
.env.production Normal file
View File

@ -0,0 +1,2 @@
NEXT_PUBLIC_SUPABASE_URL=https://cgnmpfjcbhtgdcizgfnc.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNnbm1wZmpjYmh0Z2RjaXpnZm5jIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTY4NTkyNDIsImV4cCI6MjAzMjQzNTI0Mn0.oGf2jp-Twr1qP7GQI4D87iu5EWV6UhL51OEaCLvSMxk

12
.gitignore vendored
View File

@ -14,8 +14,16 @@ yarn-error.log*
/.next
/data
/supabase/volumes/db/data/*
#local env files
.env*.local
/maildev
/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
View File

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# GitHub Copilot persisted chat sessions
/copilot/chatSessions

1
.idea/.name generated Normal file
View File

@ -0,0 +1 @@
page.tsx

17
.idea/aws.xml generated Normal file
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["denoland.vscode-deno"]
}

10
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"deno.enablePaths": [
"supabase/functions"
],
"deno.lint": true,
"deno.unstable": true,
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
}
}

View File

@ -1,29 +1,54 @@
# Neroshitron
![image](https://github.com/D4M13N-D3V/neroshitron/assets/13697702/e4a9e11b-0e52-42e0-ad9a-821a81e92e90)
![image](https://github.com/D4M13N-D3V/neroshitron/assets/13697702/78e009be-caa0-4ae6-9c06-90dde2ab4389)
## Development
### Documentation For Initial Project Setup
- https://tamagui.dev/docs/guides/next-js
# Documentation For Technical Stack
- https://nextjs.org/docs
- 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
### Running With Docker
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/
#### OwnCast
### 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.
# User Flow Diagram
![image](https://github.com/D4M13N-D3V/neroshitron/assets/13697702/57379445-8bd5-4a7e-8a15-7fa0b3ae42dc)
#### AppWrite
http://localhost:80/
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.
# Database Diagram
![image](https://github.com/D4M13N-D3V/neroshitron/assets/13697702/b3387b77-d8c6-41c4-9fe8-d52dadb6bc5e)
#### UI
http://localhost:3000
### Running Without Docker
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/

4
app/_middleware.tsx Normal file
View File

@ -0,0 +1,4 @@
import { middleware } from './middleware/path-header'
export default middleware

41
app/admin/page.tsx Normal file
View 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
View 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;

View 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
View 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;

View 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;

View 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);
}

View 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();
}
}

View 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' } });
}

View 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' },
});
}

View File

@ -0,0 +1,84 @@
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();
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);
return NextResponse.error();
}
const urls = [];
// Loop through each file, download it, convert it to base64, and add the data URL to the array
for (const file of files) {
let { data: blobdata, error } = await supabase.storage.from('galleries').download(galleryId+"/"+file.name);
if (error || blobdata==null) {
//console.error('Error downloading file:', error);
continue;
}
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();
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,${blobBuffer.toString('base64')}`;
urls.push(dataUrl);
}
// Return a JSON response with the array of URLs
return new Response(JSON.stringify(urls), { headers: { 'content-type': 'application/json' } });
}

View File

@ -0,0 +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 supabase = createClient();
const user = await supabase.auth.getUser();
const url = new URL(request.url);
const search = url.searchParams.get("nsfw");
const nsfw = search === "true";
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);
}

View 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
// }

View 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 });
}

View File

@ -0,0 +1,35 @@
import { NextResponse } from "next/server";
import { createClient } from "@/utils/supabase/server";
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('*')
.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 };
// });

View 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);
}

View 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
View 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);
}

View File

@ -0,0 +1,19 @@
import { createClient } from "@/utils/supabase/server";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
// The `/auth/callback` route is required for the server-side auth flow implemented
// by the SSR package. It exchanges an auth code for the user's session.
// https://supabase.com/docs/guides/auth/server-side/nextjs
const requestUrl = new URL(request.url);
const code = requestUrl.searchParams.get("code");
const origin = requestUrl.origin;
if (code) {
const supabase = createClient();
await supabase.auth.exchangeCodeForSession(code);
}
// URL to redirect to after sign up process completes
return NextResponse.redirect(`${origin}/protected`);
}

20
app/commissions/page.tsx Normal file
View File

@ -0,0 +1,20 @@
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
export default async function Commissions() {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return redirect("/login");
}
return (
<div className="flex-1 w-full flex flex-col gap-20 items-center animate-in">
<h1>This is protected.</h1>
</div>
);
}

BIN
app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View 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
View 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;

View 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;

58
app/gallery/page.tsx Normal file
View File

@ -0,0 +1,58 @@
"use client";
import { createClient } from "@/utils/supabase/client";
import React, { useState, useEffect } from 'react';
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 getData = async () => {
}
useEffect(() => {
getData();
}, [selectedGallery]);
const closeGallery = () => {
setSelectedGallery(null);
}
return (
<div className="w-full">
<div className="w-2/4">
<Search gallerySelected={(gallery:string)=>{setSelectedGallery(gallery)}}/>
</div>
{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>
);
}
export default PageComponent;

111
app/globals.css Normal file
View File

@ -0,0 +1,111 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@keyframes expandFromLeft {
0% {
width: 0;
}
100% {
width: 100%;
}
}
/* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
.fade-out {
animation: fadeOut 0.5s forwards;
}
@layer base {
:root {
--background: 200 20% 98%;
--btn-background: 200 10% 91%;
--btn-background-hover: 200 10% 89%;
--foreground: 200 50% 3%;
}
@media (prefers-color-scheme: dark) {
:root {
--background: 200 50% 3%;
--btn-background: 200 10% 9%;
--btn-background-hover: 200 10% 12%;
--foreground: 200 20% 96%;
}
}
}
.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 {
animation: animateIn 0.3s ease 0.15s both;
}
@keyframes animateIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
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;
}

44
app/layout.tsx Normal file
View File

@ -0,0 +1,44 @@
import { GeistSans } from "geist/font/sans";
import "./globals.css";
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";
export const metadata = {
metadataBase: new URL(defaultUrl),
title: "Next.js and Supabase Starter Kit",
description: "The fastest way to build apps with Next.js and Supabase",
};
const supabase = createClient();
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={GeistSans.className}>
<body className="bg-background text-foreground">
<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-primary to-secondary overflow-hidden">
{children}
</main>
</body>
</html>
);
}

33
app/livestream/page.tsx Normal file
View File

@ -0,0 +1,33 @@
import React, { useState, useEffect } from 'react';
function PageComponent() {
const getData = async () => {
}
return (
<div className="p-40 h-full w-full animate-in"> {/* This adds padding top of 20px */}
<div className="flex">
<iframe
className="flex-grow"
style={{flexBasis: '90%'}} // Video takes up 90% of the width
src="http://localhost:8080/embed/video"
title="Owncast"
height={720}
referrerPolicy="origin"
allowFullScreen
></iframe>
<iframe
className="flex-2"
style={{flexBasis: '10%'}} // Chat takes up 10% of the width
src="http://localhost:8080/embed/chat/readwrite"
title="Owncast"
referrerPolicy="origin"
allowFullScreen
></iframe>
</div>
</div>
);
}
export default PageComponent;

128
app/login/page.tsx Normal file
View File

@ -0,0 +1,128 @@
import Link from "next/link";
import { headers } from "next/headers";
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
import { SubmitButton } from "./submit-button";
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";
const email = formData.get("email") as string;
const password = formData.get("password") as string;
const supabase = createClient();
const { data:data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
return redirect("/login?message=Could not authenticate user");
}
return redirect("/gallery");
};
const signUp = async (formData: FormData) => {
"use server";
const origin = headers().get("origin");
const email = formData.get("email") as string;
const password = formData.get("password") as string;
const supabase = createClient();
const { error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${origin}/auth/callback`,
},
});
if (error) {
return redirect("/login?message=Could not authenticate user");
}
return redirect("/login?message=Check email to continue sign in process");
};
return (
<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-white">
<Link
href="/"
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"
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
</Link>
<input
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 w-full sm:w-auto"
type="password"
name="password"
placeholder="Password "
required
/>
<div className="flex text-white white">
<SubmitButton
formAction={signIn}
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-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-white text-center">
{searchParams.message}
</p>
)}
</form>
</div>
</div>
);
}

View File

@ -0,0 +1,20 @@
"use client";
import { useFormStatus } from "react-dom";
import { type ComponentProps } from "react";
type Props = ComponentProps<"button"> & {
pendingText?: string;
};
export function SubmitButton({ children, pendingText, ...props }: Props) {
const { pending, action } = useFormStatus();
const isPending = pending && action === props.formAction;
return (
<button {...props} type="submit" aria-disabled={pending}>
{isPending ? pendingText : children}
</button>
);
}

View 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
}
})
}

19
app/page.tsx Normal file
View File

@ -0,0 +1,19 @@
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
export default async function Index() {
const canInitSupabaseClient = () => {
// This function is just for the interactive tutorial.
// Feel free to remove it once you have Supabase connected.
try {
createClient();
return true;
} catch (e) {
return false;
}
};
const isSupabaseConnected = canInitSupabaseClient();
return redirect("/gallery")
}

20
app/protected/page.tsx Normal file
View File

@ -0,0 +1,20 @@
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
export default async function ProtectedPage() {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return redirect("/login");
}
return (
<div className="flex-1 w-full flex flex-col gap-20 items-center animate-in">
<h1>This is protected.</h1>
</div>
);
}

View File

@ -0,0 +1,20 @@
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
export default async function Subscriptions() {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return redirect("/login");
}
return (
<div className="flex-1 w-full flex flex-col gap-20 items-center animate-in">
<h1>This is protected.</h1>
</div>
);
}

View 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;

View 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;

View 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;

View 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>)
}

View 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;

View 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;

View 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;

View 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;

View 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
View 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;

20
components/ui/example.tsx Normal file
View File

@ -0,0 +1,20 @@
import { use, useState } from 'react';
import { useEffect } from 'react';
interface GalleryThumbnailProps {
}
const GalleryThumbnail = ({ }: GalleryThumbnailProps) => {
const getData = async () => {
}
useEffect(() => {
getData();
}, []);
return (
<>
</>
);
}
export default GalleryThumbnail;

View File

@ -5,842 +5,22 @@ x-logging: &x-logging
options:
max-file: '5'
max-size: '10m'
services:
neroshitron:
build:
context: .
restart: unless-stopped
ports:
- 3000:3000
# neroshitron:
# build:
# context: .
# restart: unless-stopped
# ports:
# - 3000:3000
owncast:
image: owncast/owncast:latest
restart: unless-stopped
ports:
- 8080:8080
- 1935:1935
volumes:
- ./data:/app/data
traefik:
image: traefik:2.11
container_name: appwrite-traefik
<<: *x-logging
command:
- --providers.file.directory=/storage/config
- --providers.file.watch=true
- --providers.docker=true
- --providers.docker.exposedByDefault=false
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
- --entrypoints.appwrite_web.address=:80
- --entrypoints.appwrite_websecure.address=:443
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- appwrite-config:/storage/config:ro
- appwrite-certificates:/storage/certificates:ro
depends_on:
- appwrite
networks:
- gateway
- appwrite
appwrite:
image: appwrite/appwrite:1.5.5
container_name: appwrite
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
labels:
- traefik.enable=true
- traefik.constraint-label-stack=appwrite
- traefik.docker.network=appwrite
- traefik.http.services.appwrite_api.loadbalancer.server.port=80
#http
- traefik.http.routers.appwrite_api_http.entrypoints=appwrite_web
- traefik.http.routers.appwrite_api_http.rule=PathPrefix(`/`)
- traefik.http.routers.appwrite_api_http.service=appwrite_api
# https
- traefik.http.routers.appwrite_api_https.entrypoints=appwrite_websecure
- traefik.http.routers.appwrite_api_https.rule=PathPrefix(`/`)
- traefik.http.routers.appwrite_api_https.service=appwrite_api
- traefik.http.routers.appwrite_api_https.tls=true
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
- appwrite-functions:/storage/functions:rw
depends_on:
- mariadb
- redis
# - clamav
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_LOCALE
- _APP_CONSOLE_WHITELIST_ROOT
- _APP_CONSOLE_WHITELIST_EMAILS
- _APP_CONSOLE_WHITELIST_IPS
- _APP_CONSOLE_HOSTNAMES
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_SYSTEM_RESPONSE_FORMAT
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_ROUTER_PROTECTION
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_DOMAIN_FUNCTIONS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_SMTP_HOST
- _APP_SMTP_PORT
- _APP_SMTP_SECURE
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_USAGE_STATS
- _APP_STORAGE_LIMIT
- _APP_STORAGE_PREVIEW_LIMIT
- _APP_STORAGE_ANTIVIRUS
- _APP_STORAGE_ANTIVIRUS_HOST
- _APP_STORAGE_ANTIVIRUS_PORT
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
- _APP_STORAGE_DO_SPACES_BUCKET
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
- _APP_STORAGE_BACKBLAZE_SECRET
- _APP_STORAGE_BACKBLAZE_REGION
- _APP_STORAGE_BACKBLAZE_BUCKET
- _APP_STORAGE_LINODE_ACCESS_KEY
- _APP_STORAGE_LINODE_SECRET
- _APP_STORAGE_LINODE_REGION
- _APP_STORAGE_LINODE_BUCKET
- _APP_STORAGE_WASABI_ACCESS_KEY
- _APP_STORAGE_WASABI_SECRET
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
- _APP_FUNCTIONS_SIZE_LIMIT
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_RUNTIMES
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_DELAY
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_GRAPHQL_MAX_BATCH_SIZE
- _APP_GRAPHQL_MAX_COMPLEXITY
- _APP_GRAPHQL_MAX_DEPTH
- _APP_VCS_GITHUB_APP_NAME
- _APP_VCS_GITHUB_PRIVATE_KEY
- _APP_VCS_GITHUB_APP_ID
- _APP_VCS_GITHUB_WEBHOOK_SECRET
- _APP_VCS_GITHUB_CLIENT_SECRET
- _APP_VCS_GITHUB_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
- _APP_ASSISTANT_OPENAI_API_KEY
appwrite-realtime:
image: appwrite/appwrite:1.5.5
entrypoint: realtime
container_name: appwrite-realtime
<<: *x-logging
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.constraint-label-stack=appwrite"
- "traefik.docker.network=appwrite"
- "traefik.http.services.appwrite_realtime.loadbalancer.server.port=80"
#ws
- traefik.http.routers.appwrite_realtime_ws.entrypoints=appwrite_web
- traefik.http.routers.appwrite_realtime_ws.rule=PathPrefix(`/v1/realtime`)
- traefik.http.routers.appwrite_realtime_ws.service=appwrite_realtime
# wss
- traefik.http.routers.appwrite_realtime_wss.entrypoints=appwrite_websecure
- traefik.http.routers.appwrite_realtime_wss.rule=PathPrefix(`/v1/realtime`)
- traefik.http.routers.appwrite_realtime_wss.service=appwrite_realtime
- traefik.http.routers.appwrite_realtime_wss.tls=true
networks:
- appwrite
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_ROUTER_PROTECTION
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-audits:
image: appwrite/appwrite:1.5.5
entrypoint: worker-audits
<<: *x-logging
container_name: appwrite-worker-audits
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-webhooks:
image: appwrite/appwrite:1.5.5
entrypoint: worker-webhooks
<<: *x-logging
container_name: appwrite-worker-webhooks
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-deletes:
image: appwrite/appwrite:1.5.5
entrypoint: worker-deletes
<<: *x-logging
container_name: appwrite-worker-deletes
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
- appwrite-certificates:/storage/certificates:rw
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
- _APP_STORAGE_DO_SPACES_BUCKET
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
- _APP_STORAGE_BACKBLAZE_SECRET
- _APP_STORAGE_BACKBLAZE_REGION
- _APP_STORAGE_BACKBLAZE_BUCKET
- _APP_STORAGE_LINODE_ACCESS_KEY
- _APP_STORAGE_LINODE_SECRET
- _APP_STORAGE_LINODE_REGION
- _APP_STORAGE_LINODE_BUCKET
- _APP_STORAGE_WASABI_ACCESS_KEY
- _APP_STORAGE_WASABI_SECRET
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
appwrite-worker-databases:
image: appwrite/appwrite:1.5.5
entrypoint: worker-databases
<<: *x-logging
container_name: appwrite-worker-databases
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-builds:
image: appwrite/appwrite:1.5.5
entrypoint: worker-builds
<<: *x-logging
container_name: appwrite-worker-builds
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
volumes:
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_VCS_GITHUB_APP_NAME
- _APP_VCS_GITHUB_PRIVATE_KEY
- _APP_VCS_GITHUB_APP_ID
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_SIZE_LIMIT
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
- _APP_DOMAIN
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
- _APP_STORAGE_DO_SPACES_BUCKET
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
- _APP_STORAGE_BACKBLAZE_SECRET
- _APP_STORAGE_BACKBLAZE_REGION
- _APP_STORAGE_BACKBLAZE_BUCKET
- _APP_STORAGE_LINODE_ACCESS_KEY
- _APP_STORAGE_LINODE_SECRET
- _APP_STORAGE_LINODE_REGION
- _APP_STORAGE_LINODE_BUCKET
- _APP_STORAGE_WASABI_ACCESS_KEY
- _APP_STORAGE_WASABI_SECRET
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
appwrite-worker-certificates:
image: appwrite/appwrite:1.5.5
entrypoint: worker-certificates
<<: *x-logging
container_name: appwrite-worker-certificates
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
volumes:
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_DOMAIN_FUNCTIONS
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-functions:
image: appwrite/appwrite:1.5.5
entrypoint: worker-functions
<<: *x-logging
container_name: appwrite-worker-functions
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
- openruntimes-executor
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_USAGE_STATS
- _APP_DOCKER_HUB_USERNAME
- _APP_DOCKER_HUB_PASSWORD
- _APP_LOGGING_CONFIG
- _APP_LOGGING_PROVIDER
appwrite-worker-mails:
image: appwrite/appwrite:1.5.5
entrypoint: worker-mails
<<: *x-logging
container_name: appwrite-worker-mails
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_SMTP_HOST
- _APP_SMTP_PORT
- _APP_SMTP_SECURE
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-messaging:
image: appwrite/appwrite:1.5.5
entrypoint: worker-messaging
<<: *x-logging
container_name: appwrite-worker-messaging
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_SMS_FROM
- _APP_SMS_PROVIDER
appwrite-worker-migrations:
image: appwrite/appwrite:1.5.5
entrypoint: worker-migrations
<<: *x-logging
container_name: appwrite-worker-migrations
restart: unless-stopped
networks:
- appwrite
depends_on:
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
appwrite-maintenance:
image: appwrite/appwrite:1.5.5
entrypoint: maintenance
<<: *x-logging
container_name: appwrite-maintenance
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_DOMAIN_FUNCTIONS
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_MAINTENANCE_RETENTION_SCHEDULES
appwrite-worker-usage:
image: appwrite/appwrite:1.5.5
entrypoint: worker-usage
container_name: appwrite-worker-usage
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
appwrite-worker-usage-dump:
image: appwrite/appwrite:1.5.5
entrypoint: worker-usage-dump
<<: *x-logging
container_name: appwrite-worker-usage-dump
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
appwrite-scheduler-functions:
image: appwrite/appwrite:1.5.5
entrypoint: schedule-functions
container_name: appwrite-scheduler-functions
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
appwrite-scheduler-messages:
image: appwrite/appwrite:1.5.5
entrypoint: schedule-messages
container_name: appwrite-scheduler-messages
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
appwrite-assistant:
image: appwrite/assistant:0.4.0
container_name: appwrite-assistant
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
environment:
- _APP_ASSISTANT_OPENAI_API_KEY
openruntimes-executor:
container_name: openruntimes-executor
hostname: appwrite-executor
<<: *x-logging
restart: unless-stopped
stop_signal: SIGINT
image: openruntimes/executor:0.4.12
networks:
- appwrite
- runtimes
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- appwrite-builds:/storage/builds:rw
- appwrite-functions:/storage/functions:rw
# Host mount nessessary to share files between executor and runtimes.
# It's not possible to share mount file between 2 containers without host mount (copying is too slow)
- /tmp:/tmp:rw
environment:
- OPR_EXECUTOR_INACTIVE_TRESHOLD=$_APP_FUNCTIONS_INACTIVE_THRESHOLD
- OPR_EXECUTOR_MAINTENANCE_INTERVAL=$_APP_FUNCTIONS_MAINTENANCE_INTERVAL
- OPR_EXECUTOR_NETWORK=$_APP_FUNCTIONS_RUNTIMES_NETWORK
- OPR_EXECUTOR_DOCKER_HUB_USERNAME=$_APP_DOCKER_HUB_USERNAME
- OPR_EXECUTOR_DOCKER_HUB_PASSWORD=$_APP_DOCKER_HUB_PASSWORD
- OPR_EXECUTOR_ENV=$_APP_ENV
- OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES
- OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
- OPR_EXECUTOR_LOGGING_PROVIDER=$_APP_LOGGING_PROVIDER
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
- OPR_EXECUTOR_STORAGE_DEVICE=$_APP_STORAGE_DEVICE
- OPR_EXECUTOR_STORAGE_S3_ACCESS_KEY=$_APP_STORAGE_S3_ACCESS_KEY
- OPR_EXECUTOR_STORAGE_S3_SECRET=$_APP_STORAGE_S3_SECRET
- OPR_EXECUTOR_STORAGE_S3_REGION=$_APP_STORAGE_S3_REGION
- OPR_EXECUTOR_STORAGE_S3_BUCKET=$_APP_STORAGE_S3_BUCKET
- OPR_EXECUTOR_STORAGE_DO_SPACES_ACCESS_KEY=$_APP_STORAGE_DO_SPACES_ACCESS_KEY
- OPR_EXECUTOR_STORAGE_DO_SPACES_SECRET=$_APP_STORAGE_DO_SPACES_SECRET
- OPR_EXECUTOR_STORAGE_DO_SPACES_REGION=$_APP_STORAGE_DO_SPACES_REGION
- OPR_EXECUTOR_STORAGE_DO_SPACES_BUCKET=$_APP_STORAGE_DO_SPACES_BUCKET
- OPR_EXECUTOR_STORAGE_BACKBLAZE_ACCESS_KEY=$_APP_STORAGE_BACKBLAZE_ACCESS_KEY
- OPR_EXECUTOR_STORAGE_BACKBLAZE_SECRET=$_APP_STORAGE_BACKBLAZE_SECRET
- OPR_EXECUTOR_STORAGE_BACKBLAZE_REGION=$_APP_STORAGE_BACKBLAZE_REGION
- OPR_EXECUTOR_STORAGE_BACKBLAZE_BUCKET=$_APP_STORAGE_BACKBLAZE_BUCKET
- OPR_EXECUTOR_STORAGE_LINODE_ACCESS_KEY=$_APP_STORAGE_LINODE_ACCESS_KEY
- OPR_EXECUTOR_STORAGE_LINODE_SECRET=$_APP_STORAGE_LINODE_SECRET
- OPR_EXECUTOR_STORAGE_LINODE_REGION=$_APP_STORAGE_LINODE_REGION
- OPR_EXECUTOR_STORAGE_LINODE_BUCKET=$_APP_STORAGE_LINODE_BUCKET
- OPR_EXECUTOR_STORAGE_WASABI_ACCESS_KEY=$_APP_STORAGE_WASABI_ACCESS_KEY
- OPR_EXECUTOR_STORAGE_WASABI_SECRET=$_APP_STORAGE_WASABI_SECRET
- OPR_EXECUTOR_STORAGE_WASABI_REGION=$_APP_STORAGE_WASABI_REGION
- OPR_EXECUTOR_STORAGE_WASABI_BUCKET=$_APP_STORAGE_WASABI_BUCKET
mariadb:
image: mariadb:10.11 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-mariadb:/var/lib/mysql:rw
environment:
- MYSQL_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
- MYSQL_DATABASE=${_APP_DB_SCHEMA}
- MYSQL_USER=${_APP_DB_USER}
- MYSQL_PASSWORD=${_APP_DB_PASS}
- MARIADB_AUTO_UPGRADE=1
command: 'mysqld --innodb-flush-method=fsync'
redis:
image: redis:7.2.4-alpine
container_name: appwrite-redis
<<: *x-logging
restart: unless-stopped
command: >
redis-server
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--maxmemory-samples 5
networks:
- appwrite
volumes:
- appwrite-redis:/data:rw
# clamav:
# image: appwrite/clamav:1.2.0
# container_name: appwrite-clamav
# restart: unless-stopped
# networks:
# - appwrite
# volumes:
# - appwrite-uploads:/storage/uploads
networks:
gateway:
name: gateway
appwrite:
name: appwrite
runtimes:
name: runtimes
- ./data:/owncast/data
volumes:
appwrite-mariadb:
appwrite-redis:
appwrite-cache:
appwrite-uploads:
appwrite-certificates:
appwrite-functions:
appwrite-builds:
appwrite-config:
owncast-data:
db-config:

View File

@ -1,113 +1,105 @@
_APP_ENV=production
_APP_LOCALE=en
_APP_OPTIONS_ABUSE=enabled
_APP_OPTIONS_FORCE_HTTPS=disabled
_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=disabled
_APP_OPTIONS_ROUTER_PROTECTION=disabled
_APP_OPENSSL_KEY_V1=your-secret-key
_APP_DOMAIN=localhost
_APP_DOMAIN_FUNCTIONS=functions.localhost
_APP_DOMAIN_TARGET=localhost
_APP_CONSOLE_WHITELIST_ROOT=enabled
_APP_CONSOLE_WHITELIST_EMAILS=
_APP_CONSOLE_WHITELIST_IPS=
_APP_CONSOLE_HOSTNAMES=
_APP_SYSTEM_EMAIL_NAME=Appwrite
_APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io
_APP_SYSTEM_RESPONSE_FORMAT=
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=certs@appwrite.io
_APP_USAGE_STATS=enabled
_APP_LOGGING_PROVIDER=
_APP_LOGGING_CONFIG=
_APP_USAGE_AGGREGATION_INTERVAL=30
_APP_USAGE_TIMESERIES_INTERVAL=30
_APP_USAGE_DATABASE_INTERVAL=900
_APP_WORKER_PER_CORE=6
_APP_REDIS_HOST=redis
_APP_REDIS_PORT=6379
_APP_REDIS_USER=
_APP_REDIS_PASS=
_APP_DB_HOST=mariadb
_APP_DB_PORT=3306
_APP_DB_SCHEMA=appwrite
_APP_DB_USER=user
_APP_DB_PASS=password
_APP_DB_ROOT_PASS=rootsecretpassword
_APP_INFLUXDB_HOST=influxdb
_APP_INFLUXDB_PORT=8086
_APP_STATSD_HOST=telegraf
_APP_STATSD_PORT=8125
_APP_SMTP_HOST=
_APP_SMTP_PORT=
_APP_SMTP_SECURE=
_APP_SMTP_USERNAME=
_APP_SMTP_PASSWORD=
_APP_SMS_PROVIDER=
_APP_SMS_FROM=
_APP_STORAGE_LIMIT=30000000
_APP_STORAGE_PREVIEW_LIMIT=20000000
_APP_STORAGE_ANTIVIRUS=disabled
_APP_STORAGE_ANTIVIRUS_HOST=clamav
_APP_STORAGE_ANTIVIRUS_PORT=3310
_APP_STORAGE_DEVICE=local
_APP_STORAGE_S3_ACCESS_KEY=
_APP_STORAGE_S3_SECRET=
_APP_STORAGE_S3_REGION=us-east-1
_APP_STORAGE_S3_BUCKET=
_APP_STORAGE_DO_SPACES_ACCESS_KEY=
_APP_STORAGE_DO_SPACES_SECRET=
_APP_STORAGE_DO_SPACES_REGION=us-east-1
_APP_STORAGE_DO_SPACES_BUCKET=
_APP_STORAGE_BACKBLAZE_ACCESS_KEY=
_APP_STORAGE_BACKBLAZE_SECRET=
_APP_STORAGE_BACKBLAZE_REGION=us-west-004
_APP_STORAGE_BACKBLAZE_BUCKET=
_APP_STORAGE_LINODE_ACCESS_KEY=
_APP_STORAGE_LINODE_SECRET=
_APP_STORAGE_LINODE_REGION=eu-central-1
_APP_STORAGE_LINODE_BUCKET=
_APP_STORAGE_WASABI_ACCESS_KEY=
_APP_STORAGE_WASABI_SECRET=
_APP_STORAGE_WASABI_REGION=eu-central-1
_APP_STORAGE_WASABI_BUCKET=
_APP_FUNCTIONS_SIZE_LIMIT=30000000
_APP_FUNCTIONS_TIMEOUT=900
_APP_FUNCTIONS_BUILD_TIMEOUT=900
_APP_FUNCTIONS_CONTAINERS=10
_APP_FUNCTIONS_CPUS=0
_APP_FUNCTIONS_MEMORY=0
_APP_FUNCTIONS_MEMORY_SWAP=0
_APP_FUNCTIONS_RUNTIMES=node-16.0,php-8.0,python-3.9,ruby-3.0
_APP_EXECUTOR_SECRET=your-secret-key
_APP_EXECUTOR_HOST=http://appwrite-executor/v1
_APP_EXECUTOR_RUNTIME_NETWORK=appwrite_runtimes
_APP_FUNCTIONS_ENVS=node-16.0,php-7.4,python-3.9,ruby-3.0
_APP_FUNCTIONS_INACTIVE_THRESHOLD=60
DOCKERHUB_PULL_USERNAME=
DOCKERHUB_PULL_PASSWORD=
DOCKERHUB_PULL_EMAIL=
OPEN_RUNTIMES_NETWORK=appwrite_runtimes
_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes
_APP_DOCKER_HUB_USERNAME=
_APP_DOCKER_HUB_PASSWORD=
_APP_FUNCTIONS_MAINTENANCE_INTERVAL=3600
_APP_VCS_GITHUB_APP_NAME=
_APP_VCS_GITHUB_PRIVATE_KEY=
_APP_VCS_GITHUB_APP_ID=
_APP_VCS_GITHUB_CLIENT_ID=
_APP_VCS_GITHUB_CLIENT_SECRET=
_APP_VCS_GITHUB_WEBHOOK_SECRET=
_APP_MAINTENANCE_INTERVAL=86400
_APP_MAINTENANCE_DELAY=0
_APP_MAINTENANCE_RETENTION_CACHE=2592000
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
_APP_MAINTENANCE_RETENTION_AUDIT=1209600
_APP_MAINTENANCE_RETENTION_ABUSE=86400
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
_APP_GRAPHQL_MAX_BATCH_SIZE=10
_APP_GRAPHQL_MAX_COMPLEXITY=250
_APP_GRAPHQL_MAX_DEPTH=3
_APP_MIGRATIONS_FIREBASE_CLIENT_ID=
_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=
_APP_ASSISTANT_OPENAI_API_KEY=
############
# Secrets
# YOU MUST CHANGE THESE BEFORE GOING INTO PRODUCTION
############
POSTGRES_PASSWORD=your-super-secret-and-long-postgres-password
JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long
ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q
DASHBOARD_USERNAME=supabase
DASHBOARD_PASSWORD=this_password_is_insecure_and_should_be_updated
############
# Database - You can change these to any PostgreSQL database that has logical replication enabled.
############
POSTGRES_HOST=db
POSTGRES_DB=postgres
POSTGRES_PORT=5432
# default user is postgres
############
# API Proxy - Configuration for the Kong Reverse proxy.
############
KONG_HTTP_PORT=8000
KONG_HTTPS_PORT=8443
############
# API - Configuration for PostgREST.
############
PGRST_DB_SCHEMAS=public,storage,graphql_public
############
# Auth - Configuration for the GoTrue authentication server.
############
## General
SITE_URL=http://localhost:3000
ADDITIONAL_REDIRECT_URLS=
JWT_EXPIRY=3600
DISABLE_SIGNUP=false
API_EXTERNAL_URL=http://localhost:8000
## Mailer Config
MAILER_URLPATHS_CONFIRMATION="/auth/v1/verify"
MAILER_URLPATHS_INVITE="/auth/v1/verify"
MAILER_URLPATHS_RECOVERY="/auth/v1/verify"
MAILER_URLPATHS_EMAIL_CHANGE="/auth/v1/verify"
## Email auth
ENABLE_EMAIL_SIGNUP=true
ENABLE_EMAIL_AUTOCONFIRM=false
SMTP_ADMIN_EMAIL=admin@example.com
SMTP_HOST=maildev
SMTP_PORT=1025
SMTP_USER=fake_mail_user
SMTP_PASS=fake_mail_password
SMTP_SENDER_NAME=fake_sender
ENABLE_ANONYMOUS_USERS=false
## Phone auth
ENABLE_PHONE_SIGNUP=true
ENABLE_PHONE_AUTOCONFIRM=true
############
# Studio - Configuration for the Dashboard
############
STUDIO_DEFAULT_ORGANIZATION=Default Organization
STUDIO_DEFAULT_PROJECT=Default Project
STUDIO_PORT=3000
# replace if you intend to use Studio outside of localhost
SUPABASE_PUBLIC_URL=http://localhost:8000
# Enable webp support
IMGPROXY_ENABLE_WEBP_DETECTION=true
############
# Functions - Configuration for Functions
############
# NOTE: VERIFY_JWT applies to all functions. Per-function VERIFY_JWT is not supported yet.
FUNCTIONS_VERIFY_JWT=false
############
# Logs - Configuration for Logflare
# Please refer to https://supabase.com/docs/reference/self-hosting-analytics/introduction
############
LOGFLARE_LOGGER_BACKEND_API_KEY=your-super-secret-and-long-logflare-key
# Change vector.toml sinks to reflect this change
LOGFLARE_API_KEY=your-super-secret-and-long-logflare-key
# Docker socket location - this value will differ depending on your OS
DOCKER_SOCKET_LOCATION=/var/run/docker.sock
# Google Cloud Project details
GOOGLE_PROJECT_ID=GOOGLE_PROJECT_ID
GOOGLE_PROJECT_NUMBER=GOOGLE_PROJECT_NUMBERs

503
docs/.$diagrams.drawio.bkp Normal file
View 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="&lt;h1&gt;Database Design&lt;/h1&gt;" 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="&lt;h1&gt;UX Flow&lt;/h1&gt;" 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="&lt;br&gt;Logged&lt;br&gt;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&lt;br&gt;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;amp;&lt;br&gt;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&lt;br&gt;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;amp;&amp;nbsp;&lt;br&gt;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&lt;br&gt;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&lt;br&gt;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&lt;br&gt;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&lt;br&gt;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&lt;br&gt;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:&amp;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:&amp;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>

1
docs/.obsidian/app.json vendored Normal file
View File

@ -0,0 +1 @@
{}

3
docs/.obsidian/appearance.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"accentColor": ""
}

View File

@ -0,0 +1,30 @@
{
"file-explorer": true,
"global-search": true,
"switcher": true,
"graph": true,
"backlink": true,
"canvas": true,
"outgoing-link": true,
"tag-pane": true,
"properties": false,
"page-preview": true,
"daily-notes": true,
"templates": true,
"note-composer": true,
"command-palette": true,
"slash-command": false,
"editor-status": true,
"bookmarks": true,
"markdown-importer": false,
"zk-prefixer": false,
"random-note": false,
"outline": true,
"word-count": true,
"slides": false,
"audio-recorder": false,
"workspaces": false,
"file-recovery": true,
"publish": false,
"sync": false
}

20
docs/.obsidian/core-plugins.json vendored Normal file
View File

@ -0,0 +1,20 @@
[
"file-explorer",
"global-search",
"switcher",
"graph",
"backlink",
"canvas",
"outgoing-link",
"tag-pane",
"page-preview",
"daily-notes",
"templates",
"note-composer",
"command-palette",
"editor-status",
"bookmarks",
"outline",
"word-count",
"file-recovery"
]

22
docs/.obsidian/graph.json vendored Normal file
View File

@ -0,0 +1,22 @@
{
"collapse-filter": true,
"search": "",
"showTags": false,
"showAttachments": false,
"hideUnresolved": false,
"showOrphans": true,
"collapse-color-groups": true,
"colorGroups": [],
"collapse-display": true,
"showArrow": false,
"textFadeMultiplier": 0,
"nodeSizeMultiplier": 1,
"lineSizeMultiplier": 1,
"collapse-forces": true,
"centerStrength": 0.518713248970312,
"repelStrength": 10,
"linkStrength": 1,
"linkDistance": 250,
"scale": 1,
"close": true
}

155
docs/.obsidian/workspace.json vendored Normal file
View File

@ -0,0 +1,155 @@
{
"main": {
"id": "bdde2e17ec0df42a",
"type": "split",
"children": [
{
"id": "dedd2ccd270be348",
"type": "tabs",
"children": [
{
"id": "b4a8432d18575240",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Tutorials n Documentation.md",
"mode": "source",
"source": false
}
}
}
]
}
],
"direction": "vertical"
},
"left": {
"id": "c320d60637cee9e0",
"type": "split",
"children": [
{
"id": "57d6275bac376e39",
"type": "tabs",
"children": [
{
"id": "c2afa69ba2c55138",
"type": "leaf",
"state": {
"type": "file-explorer",
"state": {
"sortOrder": "alphabetical"
}
}
},
{
"id": "81438d0c2baf979f",
"type": "leaf",
"state": {
"type": "search",
"state": {
"query": "",
"matchingCase": false,
"explainSearch": false,
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical"
}
}
},
{
"id": "956885082129707c",
"type": "leaf",
"state": {
"type": "bookmarks",
"state": {}
}
}
]
}
],
"direction": "horizontal",
"width": 300
},
"right": {
"id": "c47f5eb80efc5acc",
"type": "split",
"children": [
{
"id": "25ec1b8698cf7037",
"type": "tabs",
"children": [
{
"id": "5a51e0522b80dc8c",
"type": "leaf",
"state": {
"type": "backlink",
"state": {
"file": "Tutorials n Documentation.md",
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
"showSearch": false,
"searchQuery": "",
"backlinkCollapsed": false,
"unlinkedCollapsed": true
}
}
},
{
"id": "ce2e95cdf68332b3",
"type": "leaf",
"state": {
"type": "outgoing-link",
"state": {
"file": "Tutorials n Documentation.md",
"linksCollapsed": false,
"unlinkedCollapsed": true
}
}
},
{
"id": "dde7495b53a68d31",
"type": "leaf",
"state": {
"type": "tag",
"state": {
"sortOrder": "frequency",
"useHierarchy": true
}
}
},
{
"id": "b3cfc60f37fb32d5",
"type": "leaf",
"state": {
"type": "outline",
"state": {
"file": "Tutorials n Documentation.md"
}
}
}
]
}
],
"direction": "horizontal",
"width": 300,
"collapsed": true
},
"left-ribbon": {
"hiddenItems": {
"switcher:Open quick switcher": false,
"graph:Open graph view": false,
"canvas:Create new canvas": false,
"daily-notes:Open today's daily note": false,
"templates:Insert template": false,
"command-palette:Open command palette": false
}
},
"active": "b4a8432d18575240",
"lastOpenFiles": [
"Pasted image 20240523235540.png",
"Tutorials n Documentation.md",
"Welcome.md"
]
}

View File

@ -0,0 +1,9 @@
- https://supabase.com/docs/guides/getting-started/architecture
- https://supabase.com/docs/guides/auth/social-login/auth-discord
- https://supabase.com/docs/guides/auth/social-login/auth-twitter
- https://github.com/vercel/nextjs-subscription-payments
- https://postgrest.org/en/v12/
- https://supabase.github.io/storage/
- https://api.supabase.com/api/v1
- https://supabase.com/docs/guides/api/sql-to-rest

503
docs/diagrams.drawio Normal file
View 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="&lt;h1&gt;Database Design&lt;/h1&gt;" 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="&lt;h1&gt;UX Flow&lt;/h1&gt;" 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="&lt;br&gt;Logged&lt;br&gt;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&lt;br&gt;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;amp;&lt;br&gt;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&lt;br&gt;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;amp;&amp;nbsp;&lt;br&gt;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&lt;br&gt;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&lt;br&gt;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&lt;br&gt;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&lt;br&gt;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&lt;br&gt;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:&amp;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:&amp;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>

20
middleware.ts Normal file
View File

@ -0,0 +1,20 @@
import { type NextRequest } from "next/server";
import { updateSession } from "@/utils/supabase/middleware";
export async function middleware(request: NextRequest) {
return await updateSession(request);
}
export const config = {
matcher: [
/*
* Match all request paths except:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - images - .svg, .png, .jpg, .jpeg, .gif, .webp
* Feel free to modify this pattern to include more paths.
*/
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};

36
neroshitron/.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

93
neroshitron/README.md Normal file
View File

@ -0,0 +1,93 @@
<a href="https://demo-nextjs-with-supabase.vercel.app/">
<img alt="Next.js and Supabase Starter Kit - the fastest way to build apps with Next.js and Supabase" src="https://demo-nextjs-with-supabase.vercel.app/opengraph-image.png">
<h1 align="center">Next.js and Supabase Starter Kit</h1>
</a>
<p align="center">
The fastest way to build apps with Next.js and Supabase
</p>
<p align="center">
<a href="#features"><strong>Features</strong></a> ·
<a href="#demo"><strong>Demo</strong></a> ·
<a href="#deploy-to-vercel"><strong>Deploy to Vercel</strong></a> ·
<a href="#clone-and-run-locally"><strong>Clone and run locally</strong></a> ·
<a href="#feedback-and-issues"><strong>Feedback and issues</strong></a>
<a href="#more-supabase-examples"><strong>More Examples</strong></a>
</p>
<br/>
## Features
- Works across the entire [Next.js](https://nextjs.org) stack
- App Router
- Pages Router
- Middleware
- Client
- Server
- It just works!
- supabase-ssr. A package to configure Supabase Auth to use cookies
- Styling with [Tailwind CSS](https://tailwindcss.com)
- Optional deployment with [Supabase Vercel Integration and Vercel deploy](#deploy-your-own)
- Environment variables automatically assigned to Vercel project
## Demo
You can view a fully working demo at [demo-nextjs-with-supabase.vercel.app](https://demo-nextjs-with-supabase.vercel.app/).
## Deploy to Vercel
Vercel deployment will guide you through creating a Supabase account and project.
After installation of the Supabase integration, all relevant environment variables will be assigned to the project so the deployment is fully functioning.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-supabase&project-name=nextjs-with-supabase&repository-name=nextjs-with-supabase&demo-title=nextjs-with-supabase&demo-description=This%20starter%20configures%20Supabase%20Auth%20to%20use%20cookies%2C%20making%20the%20user's%20session%20available%20throughout%20the%20entire%20Next.js%20app%20-%20Client%20Components%2C%20Server%20Components%2C%20Route%20Handlers%2C%20Server%20Actions%20and%20Middleware.&demo-url=https%3A%2F%2Fdemo-nextjs-with-supabase.vercel.app%2F&external-id=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-supabase&demo-image=https%3A%2F%2Fdemo-nextjs-with-supabase.vercel.app%2Fopengraph-image.png&integration-ids=oac_VqOgBHqhEoFTPzGkPd7L0iH6)
The above will also clone the Starter kit to your GitHub, you can clone that locally and develop locally.
If you wish to just develop locally and not deploy to Vercel, [follow the steps below](#clone-and-run-locally).
## Clone and run locally
1. You'll first need a Supabase project which can be made [via the Supabase dashboard](https://database.new)
2. Create a Next.js app using the Supabase Starter template npx command
```bash
npx create-next-app -e with-supabase
```
3. Use `cd` to change into the app's directory
```bash
cd name-of-new-app
```
4. Rename `.env.local.example` to `.env.local` and update the following:
```
NEXT_PUBLIC_SUPABASE_URL=[INSERT SUPABASE PROJECT URL]
NEXT_PUBLIC_SUPABASE_ANON_KEY=[INSERT SUPABASE PROJECT API ANON KEY]
```
Both `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY` can be found in [your Supabase project's API settings](https://app.supabase.com/project/_/settings/api)
5. You can now run the Next.js local development server:
```bash
npm run dev
```
The starter kit should now be running on [localhost:3000](http://localhost:3000/).
> Check out [the docs for Local Development](https://supabase.com/docs/guides/getting-started/local-development) to also run Supabase locally.
## Feedback and issues
Please file feedback and issues over on the [Supabase GitHub org](https://github.com/supabase/supabase/issues/new/choose).
## More Supabase examples
- [Next.js Subscription Payments Starter](https://github.com/vercel/nextjs-subscription-payments)
- [Cookie-based Auth and the Next.js 13 App Router (free course)](https://youtube.com/playlist?list=PL5S4mPUpp4OtMhpnp93EFSo42iQ40XjbF)
- [Supabase Auth and the Next.js App Router](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs)

View File

@ -1,16 +1,4 @@
const { withTamagui } = require('@tamagui/next-plugin')
/** @type {import('next').NextConfig} */
const nextConfig = {};
module.exports = function (name, { defaultConfig }) {
let config = {
...defaultConfig,
// ...your configuration
}
const tamaguiPlugin = withTamagui({
config: './tamagui.config.ts',
components: ['tamagui'],
})
return {
...config,
...tamaguiPlugin(config),
}
}
module.exports = nextConfig;

15091
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,44 @@
{
"dependencies": {
"@tamagui/config": "^1.98.2",
"@tamagui/next-plugin": "^1.98.2",
"@tamagui/next-theme": "^1.98.2",
"next": "^14.2.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-native-web": "^0.19.11",
"tamagui": "^1.98.2"
},
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"start": "next start"
},
"dependencies": {
"@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",
"geist": "^1.2.1",
"md5": "^2.3.0",
"next": "latest",
"postcss": "8.4.33",
"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",
"tailwindcss-animated": "^1.0.1",
"tailwindcss-textshadow": "^2.1.3",
"typescript": "5.3.3",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/react": "18.3.2",
"typescript": "5.4.5"
"@types/node": "20.11.5",
"@types/react": "18.2.48",
"@types/react-dom": "18.2.18",
"encoding": "^0.1.13"
}
}

View File

@ -1,69 +0,0 @@
// Optional: add the reset to get more consistent styles across browsers
import '@tamagui/core/reset.css'
import { NextThemeProvider, useRootTheme } from '@tamagui/next-theme'
import { AppProps } from 'next/app'
import Head from 'next/head'
import React, { useMemo } from 'react'
import { TamaguiProvider, createTamagui } from 'tamagui'
import { config } from '@tamagui/config/v3'
const tamaguiConfig = createTamagui(config)
// you usually export this from a tamagui.config.ts file:
// import tamaguiConfig from '../tamagui.config'
// make TypeScript type everything based on your config
type Conf = typeof tamaguiConfig
declare module '@tamagui/core' {
interface TamaguiCustomConfig extends Conf {}
}
export default function App({ Component, pageProps }: AppProps) {
const [theme, setTheme] = useRootTheme()
// memo to avoid re-render on dark/light change
const contents = useMemo(() => {
return <Component {...pageProps} />
}, [pageProps])
return (
<NextThemeProvider onChangeTheme={setTheme as any}>
<Head>
<script
dangerouslySetInnerHTML={{
// avoid flash of animated things on enter:
__html: `document.documentElement.classList.add('t_unmounted')`,
}}
/>
</Head>
<TamaguiProvider
config={tamaguiConfig}
disableInjectCSS
disableRootThemeClass
defaultTheme={theme}
>
{contents}
</TamaguiProvider>
</NextThemeProvider>
)
}

View File

@ -1,89 +0,0 @@
import NextDocument, {
DocumentContext,
Head,
Html,
Main,
NextScript,
} from 'next/document'
import { StyleSheet } from 'react-native'
import { config } from '@tamagui/config/v3'
import { createTamagui } from 'tamagui'
const tamaguiConfig = createTamagui(config)
// you usually export this from a tamagui.config.ts file:
// import tamaguiConfig from '../tamagui.config'
export default class Document extends NextDocument {
static async getInitialProps({ renderPage }: DocumentContext) {
const page = await renderPage()
// @ts-ignore RN doesn't have this type
const rnwStyle = StyleSheet.getSheet()
return {
...page,
styles: (
<>
<style
id={rnwStyle.id}
dangerouslySetInnerHTML={{ __html: rnwStyle.textContent }}
/>
<style
dangerouslySetInnerHTML={{
__html: tamaguiConfig.getCSS(),
}}
/>
</>
),
}
}
render() {
return (
<Html lang="en">
<Head>
<meta id="theme-color" name="theme-color" />
<meta name="color-scheme" content="light dark" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}

View File

@ -1,19 +0,0 @@
export default async function handler(req, res): Promise<any> {
let jsonString = generateRandomStringsAndJsonify(10, 5);
res.status(200).json(jsonString);
}
function generateRandomStringsAndJsonify(length, stringLength) {
let result = [];
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < length; i++) {
let randomString = '';
for (let j = 0; j < stringLength; j++) {
randomString += characters.charAt(Math.floor(Math.random() * characters.length));
}
result.push(randomString);
}
return JSON.stringify(result);
}

View File

@ -1,18 +0,0 @@
import { useState } from 'react'
import { Button, useIsomorphicLayoutEffect } from 'tamagui'
import { useThemeSetting } from '@tamagui/next-theme'
export default function Home() {
const themeSetting = useThemeSetting()
const [clientTheme, setClientTheme] = useState<string>('dark')
useIsomorphicLayoutEffect(() => {
setClientTheme(themeSetting.current || 'dark')
}, [themeSetting.current, themeSetting.resolvedTheme])
return <Button onPress={themeSetting.toggle}>Change theme: {clientTheme}</Button>
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

BIN
public/gallery_girl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

4
supabase/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Supabase
.branches
.temp
.env

171
supabase/config.toml Normal file
View 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)"

View 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
View 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;

48
tailwind.config.js Normal file
View File

@ -0,0 +1,48 @@
/** @type {import('tailwindcss').Config} */
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: {
textStrokeColor: {
'black': '#000',
},
textStrokeWidth: {
'1': '1px',
},
aspectRatio: {}, // enable aspect-ratio plugin
textShadow: {
// 'pink-glow': '0 0 4px #524FFD, 0 0 4px #524FFD, 0 0 4px #524FFD, 0 0 4px #524FFD',
},
colors: {
'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)',
},
},
},
plugins: [
require('tailwindcss-animated'),
require('tailwindcss-textshadow'),
require('@tailwindcss/aspect-ratio')
],
};

View File

@ -1,14 +0,0 @@
import { config } from '@tamagui/config/v3'
import { createTamagui } from 'tamagui'
const tamaguiConfig = createTamagui(config)
// this makes typescript properly type everything based on the config
type Conf = typeof tamaguiConfig
declare module 'tamagui' {
interface TamaguiCustomConfig extends Conf {}
}
export default tamaguiConfig

View File

@ -1,28 +1,28 @@
{
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"module": "esnext",
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules","supabase/volumes/functions/**/*"]
}

3
types/react-easy-panzoom.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
// types/react-easy-panzoom.d.ts
declare module 'react-easy-panzoom';

6
utils/cn.ts Normal file
View File

@ -0,0 +1,6 @@
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

7
utils/supabase/client.ts Normal file
View File

@ -0,0 +1,7 @@
import { createBrowserClient } from "@supabase/ssr";
export const createClient = () =>
createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);

View File

@ -0,0 +1,78 @@
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { type NextRequest, NextResponse } from "next/server";
export const updateSession = async (request: NextRequest) => {
// This `try/catch` block is only here for the interactive tutorial.
// Feel free to remove once you have Supabase connected.
try {
// Create an unmodified response
let response = NextResponse.next({
request: {
headers: request.headers,
},
});
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return request.cookies.get(name)?.value;
},
set(name: string, value: string, options: CookieOptions) {
// If the cookie is updated, update the cookies for the request and response
request.cookies.set({
name,
value,
...options,
});
response = NextResponse.next({
request: {
headers: request.headers,
},
});
response.cookies.set({
name,
value,
...options,
});
},
remove(name: string, options: CookieOptions) {
// If the cookie is removed, update the cookies for the request and response
request.cookies.set({
name,
value: "",
...options,
});
response = NextResponse.next({
request: {
headers: request.headers,
},
});
response.cookies.set({
name,
value: "",
...options,
});
},
},
},
);
// This will refresh session if expired - required for Server Components
// https://supabase.com/docs/guides/auth/server-side/nextjs
await supabase.auth.getUser();
return response;
} catch (e) {
// If you are here, a Supabase client could not be created!
// This is likely because you have not set up environment variables.
// Check out http://localhost:3000 for Next Steps.
return NextResponse.next({
request: {
headers: request.headers,
},
});
}
};

36
utils/supabase/server.ts Normal file
View File

@ -0,0 +1,36 @@
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { cookies } from "next/headers";
export const createClient = () => {
const cookieStore = cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value;
},
set(name: string, value: string, options: CookieOptions) {
try {
cookieStore.set({ name, value, ...options });
} catch (error) {
// The `set` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
remove(name: string, options: CookieOptions) {
try {
cookieStore.set({ name, value: "", ...options });
} catch (error) {
// The `delete` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
},
},
);
};