From d60c6ac3bba5164d0db448579efdd4388fe2eee1 Mon Sep 17 00:00:00 2001 From: Chris Grimmett Date: Sat, 20 Jan 2024 08:16:14 -0800 Subject: [PATCH] init --- .dockerignore | 15 + .gitignore | 148 + Dockerfile | 19 + compose.prod.yml | 103 + compose.yml | 190 + packages/next/.eslintrc.json | 3 + packages/next/.gitignore | 47 + packages/next/.nvmrc | 1 + packages/next/CHECKS | 1 + packages/next/Dockerfile.old | 35 + packages/next/LICENSE | 21 + packages/next/README.md | 27 + packages/next/app.json | 14 + packages/next/app/about/page.tsx | 64 + packages/next/app/api/blogs/route.ts | 10 + packages/next/app/api/page.tsx | 118 + packages/next/app/api/revalidate/route.ts | 26 + packages/next/app/api/service.json/route.ts | 139 + packages/next/app/api/v1.json/route.ts | 91 + .../page.tsx | 54 + packages/next/app/blog/page.tsx | 35 + .../next/app/components/archive-progress.tsx | 23 + packages/next/app/components/auth.tsx | 131 + packages/next/app/components/cal.tsx | 125 + packages/next/app/components/contributors.tsx | 33 + .../next/app/components/custom-hls-player.tsx | 80 + packages/next/app/components/footer.tsx | 118 + packages/next/app/components/funding-goal.tsx | 81 + packages/next/app/components/icons/carrd.tsx | 8 + .../next/app/components/icons/chaturbate.tsx | 14 + packages/next/app/components/icons/fansly.tsx | 20 + .../next/app/components/icons/linktree.tsx | 30 + .../next/app/components/icons/onlyfans.tsx | 55 + .../next/app/components/icons/pornhub.tsx | 23 + packages/next/app/components/icons/throne.tsx | 31 + packages/next/app/components/ipfs-cid.tsx | 42 + packages/next/app/components/ipfs-logo.tsx | 18 + packages/next/app/components/ipfs.tsx | 39 + .../next/app/components/linkable-heading.tsx | 28 + .../next/app/components/localized-date.tsx | 15 + packages/next/app/components/navbar.tsx | 98 + .../app/components/notification-center.tsx | 13 + .../next/app/components/notifications.tsx | 10 + packages/next/app/components/pager.tsx | 82 + packages/next/app/components/patrons-list.tsx | 55 + .../next/app/components/sortable-tags.tsx | 70 + .../next/app/components/stream-button.tsx | 19 + packages/next/app/components/stream-page.tsx | 187 + packages/next/app/components/stream.tsx | 86 + .../next/app/components/streams-calendar.tsx | 83 + packages/next/app/components/streams-list.tsx | 61 + packages/next/app/components/tag-button.tsx | 8 + packages/next/app/components/tag.tsx | 57 + packages/next/app/components/tagger.tsx | 240 + .../next/app/components/timestamps-list.tsx | 72 + packages/next/app/components/toys.tsx | 81 + packages/next/app/components/upload-form.tsx | 327 + .../next/app/components/user-controls.tsx | 229 + .../next/app/components/video-context.tsx | 56 + .../next/app/components/video-interactive.tsx | 134 + packages/next/app/components/video-player.tsx | 151 + .../app/components/video-source-selector.tsx | 130 + packages/next/app/components/vod-card.tsx | 72 + packages/next/app/components/vod-nav.tsx | 90 + packages/next/app/components/vod-page.tsx | 96 + packages/next/app/components/vods-list.tsx | 66 + .../next/app/components/vtuber-button.tsx | 29 + packages/next/app/components/vtuber-card.tsx | 43 + .../app/connect/patreon/redirect/page.tsx | 123 + packages/next/app/faq/page.tsx | 104 + packages/next/app/favicon.ico | Bin 0 -> 318 bytes packages/next/app/feed/feed.json/route.ts | 11 + packages/next/app/feed/feed.xml/route.ts | 11 + packages/next/app/feed/page.tsx | 28 + packages/next/app/feed/rss.xml/route.ts | 11 + packages/next/app/goals/page.tsx | 75 + packages/next/app/health/page.tsx | 12 + packages/next/app/latest-vods/[page]/page.tsx | 32 + packages/next/app/latest-vods/page.tsx | 24 + packages/next/app/layout.tsx | 70 + packages/next/app/lib/b2File.ts | 15 + packages/next/app/lib/blog.ts | 5 + packages/next/app/lib/constants.ts | 20 + packages/next/app/lib/contributors.ts | 24 + packages/next/app/lib/dates.ts | 59 + packages/next/app/lib/fetch-api.ts | 34 + packages/next/app/lib/fetchers.ts | 32 + packages/next/app/lib/ipfs.ts | 7 + packages/next/app/lib/patreon.ts | 55 + packages/next/app/lib/pm.ts | 139 + packages/next/app/lib/retry.ts | 13 + packages/next/app/lib/rss.ts | 51 + packages/next/app/lib/shareRef.ts | 16 + packages/next/app/lib/streams.ts | 369 + packages/next/app/lib/tag-vod-relations.ts | 191 + packages/next/app/lib/tags.ts | 139 + packages/next/app/lib/timestamps.ts | 127 + packages/next/app/lib/toys.ts | 42 + packages/next/app/lib/tweets.ts | 28 + packages/next/app/lib/types.ts | 28 + packages/next/app/lib/useForwardRef.ts | 27 + packages/next/app/lib/users.ts | 16 + packages/next/app/lib/vods.ts | 502 + packages/next/app/lib/vtubers.ts | 171 + packages/next/app/page.tsx | 72 + packages/next/app/patrons/page.tsx | 42 + packages/next/app/profile/page.tsx | 62 + .../next/app/streams/[cuid]/not-found.tsx | 12 + packages/next/app/streams/[cuid]/page.tsx | 20 + packages/next/app/streams/page.tsx | 34 + packages/next/app/tags/[slug]/page.tsx | 35 + packages/next/app/tags/page.tsx | 15 + packages/next/app/upload/page.tsx | 26 + packages/next/app/upload/page.tsx.old | 240 + packages/next/app/uppy.tsx | 45 + .../next/app/vods/[safeDateOrCuid]/page.tsx | 16 + packages/next/app/vods/page.tsx | 6 + packages/next/app/vt/[slug]/history/page.tsx | 70 + packages/next/app/vt/[slug]/not-found.tsx | 12 + packages/next/app/vt/[slug]/page.tsx | 246 + .../app/vt/[slug]/stream/[safeDate]/page.tsx | 31 + packages/next/app/vt/[slug]/streams/page.tsx | 23 + .../next/app/vt/[slug]/toys/[page]/page.tsx | 33 + packages/next/app/vt/[slug]/toys/page.tsx | 33 + .../vt/[slug]/vod/[safeDateOrCuid]/page.tsx | 12 + packages/next/app/vt/[slug]/vod/page.tsx | 15 + .../next/app/vt/[slug]/vods/[page]/page.tsx | 43 + packages/next/app/vt/[slug]/vods/page.tsx | 26 + packages/next/app/vt/page.tsx | 30 + .../styles/calendar-heatmap.module.scss | 89 + packages/next/assets/styles/cid.module.css | 19 + packages/next/assets/styles/fp.module.css | 20 + packages/next/assets/styles/global.sass | 50 + packages/next/assets/styles/icon.module.css | 20 + packages/next/assets/styles/player.module.css | 4 + packages/next/assets/svg/README.md | 5 + packages/next/assets/svg/carrd.svg | 1 + packages/next/assets/svg/chaturbate.svg | 15 + packages/next/assets/svg/checkmark.svg | 5 + packages/next/assets/svg/fansly.tsx | 20 + packages/next/assets/svg/ipfs.svg | 1 + packages/next/assets/svg/linktree.svg | 64 + .../assets/svg/noun-adult-content-1731184.svg | 1 + .../next/assets/svg/noun-anime-3890912.svg | 1 + .../next/assets/svg/noun-avatar-3546974.svg | 1 + packages/next/assets/svg/noun-girl-842331.svg | 4 + .../next/assets/svg/noun-network-1603820.svg | 1 + packages/next/assets/svg/onlyfans.svg | 9 + packages/next/assets/svg/pornhub.svg | 5 + packages/next/assets/svg/throne.svg | 12 + packages/next/next.config.js | 22 + packages/next/package.json | 79 + packages/next/public/futureporn-icon.png | Bin 0 -> 11599 bytes packages/next/public/images/.keep | 0 packages/next/public/images/cj_clippy.jpg | Bin 0 -> 301528 bytes .../next/public/images/default-thumbnail.webp | Bin 0 -> 27268 bytes .../next/public/images/projekt-melody.jpg | Bin 0 -> 134526 bytes .../images/projektmelody-thumbnail.webp | Bin 0 -> 1356 bytes packages/next/public/images/vercel.svg | 3 + packages/next/tsconfig.json | 50 + packages/strapi/.dockerignore | 8 + packages/strapi/.gitignore | 118 + packages/strapi/.nvmrc | 1 + packages/strapi/.strapi/client/app.js | 14 + packages/strapi/.strapi/client/index.html | 62 + packages/strapi/Dockerfile | 19 + packages/strapi/README.md | 7 + .../strapi/backup/Dockerfile.1704607848934 | 49 + packages/strapi/chisel.sh | 5 + packages/strapi/config/admin.js | 13 + packages/strapi/config/api.js | 7 + packages/strapi/config/database.js | 49 + packages/strapi/config/middlewares.js | 26 + packages/strapi/config/plugins.js | 75 + packages/strapi/config/server.js | 15 + packages/strapi/database/daily-backup.sh | 13 + packages/strapi/database/devDb.sh | 22 + packages/strapi/database/migrations/.gitkeep | 0 ...2023-08-01-relate-vods-to-vtubers-part2.js | 25 + .../migrations/2023-08-17-reformat-cdnUrl.js | 18 + .../2023-08-20-strip-query-string-from-cid.js | 23 + .../2023-08-30-remove-cloudinary.js | 13 + .../2023-08-30-toy-image-field-simplify.js | 33 + .../2023-09-08-change-date-to-string.js | 23 + .../migrations/2023-09-08-drop-toys-image.js | 11 + .../2023-09-08-drop-vod-videosrc.js | 11 + .../migrations/2023-12-24-add-cuid-to-vods.js | 31 + .../2023-12-26-add-cuid-to-streams.js | 33 + .../2023-12-27-relate-vods-to-streams.js | 35 + .../2023.05.09-video-src-sanity.js.noexec | 26 + ...12.32.00.convert-to-video-src-b2.js.noexec | 98 + ....000Z.migrate-tags-to-tag-vod-relations.js | 43 + .../2023.05.15T02.44.00.000Z.drop-vod-tags.js | 12 + .../2023.05.25-gimme-the-tags.js.noexec | 110 + ...023.05.25T20.44.00.000Z.get-the-og-tags.js | 124 + .../2023.07.17.relate-vods-to-vtubers.js | 70 + .../2023.07.31.add-b2-file-cdnUrl.js | 18 + .../2024-01-08-add-streams.js.noexec | 30 + .../2024-01-14-add-date2-to-streams.js | 29 + .../2024-01-15-add-platform-to-streams.js | 49 + packages/strapi/database/og-tags.json | 1009 ++ packages/strapi/favicon.png | Bin 0 -> 497 bytes .../strapi/misc/2023-05-26-export-og-tags.js | 202 + packages/strapi/misc/generateCuid.js | 7 + packages/strapi/package.json | 90 + packages/strapi/public/robots.txt | 3 + packages/strapi/public/uploads/.gitkeep | 0 packages/strapi/src/admin/app.example.js | 39 + .../src/admin/webpack.config.example.js | 9 + packages/strapi/src/api/.gitkeep | 0 .../b2-file/content-types/b2-file/schema.json | 36 + .../src/api/b2-file/controllers/b2-file.js | 9 + .../strapi/src/api/b2-file/routes/b2-file.js | 9 + .../src/api/b2-file/services/b2-file.js | 9 + .../content-types/contributor/schema.json | 30 + .../contributor/controllers/contributor.js | 9 + .../src/api/contributor/routes/contributor.js | 9 + .../api/contributor/services/contributor.js | 9 + .../api/goal/content-types/goal/schema.json | 30 + .../strapi/src/api/goal/controllers/goal.js | 9 + packages/strapi/src/api/goal/routes/goal.js | 9 + packages/strapi/src/api/goal/services/goal.js | 11 + .../api/gogs/content-types/gogs/schema.json | 24 + .../strapi/src/api/gogs/controllers/gogs.js | 29 + packages/strapi/src/api/gogs/routes/gogs.js | 34 + packages/strapi/src/api/gogs/services/gogs.js | 42 + .../api/issue/content-types/issue/schema.json | 36 + .../strapi/src/api/issue/controllers/issue.js | 9 + packages/strapi/src/api/issue/routes/issue.js | 9 + .../strapi/src/api/issue/services/issue.js | 9 + .../content-types/mux-asset/schema.json | 28 + .../api/mux-asset/controllers/mux-asset.js | 56 + .../src/api/mux-asset/routes/mux-asset.js | 33 + .../src/api/mux-asset/services/mux-asset.js | 78 + .../patreon/content-types/patreon/schema.json | 39 + .../src/api/patreon/controllers/patreon.js | 37 + .../strapi/src/api/patreon/routes/patreon.js | 37 + .../src/api/patreon/services/patreon.js | 45 + .../src/api/profile/controllers/profile.js | 21 + .../strapi/src/api/profile/routes/profile.js | 23 + .../src/api/profile/services/profile.js | 7 + .../stream/content-types/stream/lifecycles.js | 61 + .../stream/content-types/stream/schema.json | 73 + .../src/api/stream/controllers/stream.js | 9 + .../strapi/src/api/stream/routes/stream.js | 9 + .../strapi/src/api/stream/services/stream.js | 9 + .../tag-vod-relation/schema.json | 39 + .../controllers/tag-vod-relation.js | 222 + .../routes/tag-vod-relation.js | 51 + .../services/tag-vod-relation.js | 69 + .../src/api/tag/content-types/tag/schema.json | 38 + .../strapi/src/api/tag/controllers/tag.js | 87 + packages/strapi/src/api/tag/routes/tag.js | 37 + packages/strapi/src/api/tag/services/tag.js | 46 + .../content-types/timestamp/schema.json | 47 + .../api/timestamp/controllers/timestamp.js | 166 + .../src/api/timestamp/routes/timestamp.js | 53 + .../src/api/timestamp/services/timestamp.js | 44 + .../src/api/toy/content-types/toy/schema.json | 51 + .../strapi/src/api/toy/controllers/toy.js | 9 + packages/strapi/src/api/toy/routes/toy.js | 9 + packages/strapi/src/api/toy/services/toy.js | 9 + .../tweet/content-types/tweet/lifecycles.js | 136 + .../api/tweet/content-types/tweet/schema.json | 49 + .../strapi/src/api/tweet/controllers/tweet.js | 9 + packages/strapi/src/api/tweet/routes/tweet.js | 9 + .../strapi/src/api/tweet/services/tweet.js | 9 + .../user-submitted-content/lifecycles.js | 58 + .../user-submitted-content/schema.json | 36 + .../controllers/user-submitted-content.js | 60 + .../routes/user-submitted-content.js | 33 + .../services/user-submitted-content.js | 9 + .../src/api/vod/content-types/lifecycles.js | 12 + .../src/api/vod/content-types/vod/schema.json | 142 + .../strapi/src/api/vod/controllers/vod.js | 85 + packages/strapi/src/api/vod/routes/vod.js | 38 + packages/strapi/src/api/vod/services/vod.js | 9 + .../vtuber/content-types/vtuber/lifecycles.js | 23 + .../vtuber/content-types/vtuber/schema.json | 118 + .../src/api/vtuber/controllers/vtuber.js | 9 + .../strapi/src/api/vtuber/routes/vtuber.js | 9 + .../strapi/src/api/vtuber/services/vtuber.js | 9 + packages/strapi/src/extensions/.gitkeep | 0 .../users-permissions/.eslintignore | 2 + .../extensions/users-permissions/.eslintrc.js | 14 + .../src/extensions/users-permissions/LICENSE | 22 + .../extensions/users-permissions/README.md | 1 + .../components/BoundRoute/getMethodColor.js | 41 + .../admin/src/components/BoundRoute/index.js | 72 + .../src/components/FormModal/Input/index.js | 123 + .../admin/src/components/FormModal/index.js | 126 + .../PermissionRow/CheckboxWrapper.js | 30 + .../Permissions/PermissionRow/SubCategory.js | 131 + .../Permissions/PermissionRow/index.js | 55 + .../admin/src/components/Permissions/index.js | 57 + .../admin/src/components/Permissions/init.js | 9 + .../src/components/Permissions/reducer.js | 27 + .../admin/src/components/Policies/index.js | 62 + .../src/components/UsersPermissions/index.js | 95 + .../src/components/UsersPermissions/init.js | 10 + .../components/UsersPermissions/reducer.js | 62 + .../contexts/UsersPermissionsContext/index.js | 18 + .../admin/src/hooks/index.js | 5 + .../admin/src/hooks/useFetchRole/index.js | 67 + .../admin/src/hooks/useFetchRole/reducer.js | 31 + .../admin/src/hooks/useForm/index.js | 70 + .../admin/src/hooks/useForm/reducer.js | 40 + .../admin/src/hooks/usePlugins.js | 71 + .../admin/src/hooks/useRolesList/index.js | 65 + .../admin/src/hooks/useRolesList/init.js | 5 + .../admin/src/hooks/useRolesList/reducer.js | 31 + .../users-permissions/admin/src/index.js | 125 + .../admin/src/pages/AdvancedSettings/index.js | 246 + .../src/pages/AdvancedSettings/utils/api.js | 18 + .../pages/AdvancedSettings/utils/layout.js | 96 + .../pages/AdvancedSettings/utils/schema.js | 19 + .../EmailTemplates/components/EmailForm.js | 176 + .../EmailTemplates/components/EmailTable.js | 128 + .../admin/src/pages/EmailTemplates/index.js | 163 + .../src/pages/EmailTemplates/utils/api.js | 18 + .../src/pages/EmailTemplates/utils/schema.js | 22 + .../admin/src/pages/Providers/index.js | 275 + .../admin/src/pages/Providers/reducer.js | 54 + .../admin/src/pages/Providers/utils/api.js | 26 + .../Providers/utils/createProvidersArray.js | 21 + .../admin/src/pages/Providers/utils/forms.js | 259 + .../admin/src/pages/Roles/CreatePage.js | 185 + .../admin/src/pages/Roles/EditPage.js | 197 + .../Roles/ListPage/components/TableBody.js | 93 + .../admin/src/pages/Roles/ListPage/index.js | 243 + .../src/pages/Roles/ListPage/utils/api.js | 32 + .../src/pages/Roles/ProtectedCreatePage.js | 15 + .../src/pages/Roles/ProtectedEditPage.js | 15 + .../src/pages/Roles/ProtectedListPage.js | 17 + .../admin/src/pages/Roles/constants.js | 7 + .../admin/src/pages/Roles/index.js | 30 + .../admin/src/permissions.js | 31 + .../users-permissions/admin/src/pluginId.js | 5 + .../admin/src/translations/ar.json | 40 + .../admin/src/translations/cs.json | 46 + .../admin/src/translations/de.json | 58 + .../admin/src/translations/dk.json | 82 + .../admin/src/translations/en.json | 82 + .../admin/src/translations/es.json | 82 + .../admin/src/translations/fr.json | 46 + .../admin/src/translations/id.json | 58 + .../admin/src/translations/it.json | 58 + .../admin/src/translations/ja.json | 44 + .../admin/src/translations/ko.json | 82 + .../admin/src/translations/ms.json | 45 + .../admin/src/translations/nl.json | 44 + .../admin/src/translations/pl.json | 82 + .../admin/src/translations/pt-BR.json | 40 + .../admin/src/translations/pt.json | 44 + .../admin/src/translations/ru.json | 82 + .../admin/src/translations/sk.json | 46 + .../admin/src/translations/sv.json | 82 + .../admin/src/translations/th.json | 56 + .../admin/src/translations/tr.json | 81 + .../admin/src/translations/uk.json | 45 + .../admin/src/translations/vi.json | 46 + .../admin/src/translations/zh-Hans.json | 82 + .../admin/src/translations/zh.json | 82 + .../admin/src/utils/cleanPermissions.js | 25 + .../admin/src/utils/formatPluginName.js | 26 + .../admin/src/utils/formatPolicies.js | 8 + .../admin/src/utils/getRequestURL.js | 5 + .../admin/src/utils/getTrad.js | 5 + .../admin/src/utils/index.js | 4 + .../content-types/user/schema.json | 88 + .../documentation/content-api.yaml | 878 ++ .../users-permissions/jest.config.front.js | 7 + .../extensions/users-permissions/package.json | 81 + .../server/bootstrap/grant-config.js | 131 + .../server/bootstrap/index.js | 133 + .../bootstrap/users-permissions-actions.js | 80 + .../users-permissions/server/config.js | 23 + .../server/content-types/index.js | 11 + .../server/content-types/permission/index.js | 34 + .../server/content-types/role/index.js | 51 + .../server/content-types/user/index.js | 75 + .../content-types/user/schema-config.js | 15 + .../server/controllers/auth.js | 416 + .../controllers/content-manager-user.js | 175 + .../server/controllers/index.js | 17 + .../server/controllers/permissions.js | 26 + .../server/controllers/role.js | 77 + .../server/controllers/settings.js | 85 + .../server/controllers/user.js | 209 + .../server/controllers/validation/auth.js | 57 + .../controllers/validation/email-template.js | 74 + .../server/controllers/validation/user.js | 59 + .../users-permissions/server/graphql/index.js | 44 + .../graphql/mutations/auth/change-password.js | 38 + .../mutations/auth/email-confirmation.js | 39 + .../graphql/mutations/auth/forgot-password.js | 35 + .../server/graphql/mutations/auth/login.js | 35 + .../server/graphql/mutations/auth/register.js | 36 + .../graphql/mutations/auth/reset-password.js | 38 + .../mutations/crud/role/create-role.js | 34 + .../mutations/crud/role/delete-role.js | 25 + .../mutations/crud/role/update-role.js | 35 + .../mutations/crud/user/create-user.js | 45 + .../mutations/crud/user/delete-user.js | 39 + .../mutations/crud/user/update-user.js | 46 + .../server/graphql/mutations/index.js | 43 + .../server/graphql/queries/index.js | 13 + .../server/graphql/queries/me.js | 17 + .../server/graphql/resolvers-configs.js | 42 + .../graphql/types/create-role-payload.js | 11 + .../graphql/types/delete-role-payload.js | 11 + .../server/graphql/types/index.js | 21 + .../server/graphql/types/login-input.js | 13 + .../server/graphql/types/login-payload.js | 12 + .../server/graphql/types/me-role.js | 14 + .../server/graphql/types/me.js | 16 + .../server/graphql/types/password-payload.js | 11 + .../server/graphql/types/register-input.js | 13 + .../graphql/types/update-role-payload.js | 11 + .../users-permissions/server/graphql/utils.js | 27 + .../users-permissions/server/index.js | 21 + .../server/middlewares/index.js | 7 + .../server/middlewares/rateLimit.js | 27 + .../users-permissions/server/register.js | 29 + .../server/routes/admin/index.js | 10 + .../server/routes/admin/permissions.js | 20 + .../server/routes/admin/role.js | 79 + .../server/routes/admin/settings.js | 95 + .../server/routes/content-api/auth.js | 82 + .../server/routes/content-api/index.js | 11 + .../server/routes/content-api/permissions.js | 9 + .../server/routes/content-api/role.js | 29 + .../server/routes/content-api/user.js | 60 + .../users-permissions/server/routes/index.js | 6 + .../server/services/index.js | 19 + .../users-permissions/server/services/jwt.js | 79 + .../server/services/permission.js | 45 + .../server/services/providers-registry.js | 382 + .../server/services/providers.js | 156 + .../users-permissions/server/services/role.js | 177 + .../users-permissions/server/services/user.js | 148 + .../server/services/users-permissions.js | 249 + .../server/strategies/users-permissions.js | 114 + .../users-permissions/server/utils/index.d.ts | 18 + .../users-permissions/server/utils/index.js | 12 + .../server/utils/sanitize/index.js | 9 + .../server/utils/sanitize/sanitizers.js | 19 + .../server/utils/sanitize/visitors/index.js | 5 + ...remove-user-relation-from-role-entities.js | 11 + .../users-permissions/strapi-admin.js | 3 + .../users-permissions/strapi-server.js | 12 + packages/strapi/src/index.js | 80 + .../strapi/src/policies/updateOwnerOnly.js | 34 + packages/strapi/yarn.lock | 11853 ++++++++++++++++ packages/uppy/.env.old | 30 + packages/uppy/.env.production | 17 + packages/uppy/.gitignore | 145 + packages/uppy/Dockerfile | 15 + packages/uppy/README.md | 14 + packages/uppy/apply-backblaze-cors-rules.sh | 8 + packages/uppy/index.js | 124 + packages/uppy/package.json | 31 + pnpm-lock.yaml | 6817 +++++++++ pnpm-workspace.yaml | 5 + 464 files changed, 44681 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 compose.prod.yml create mode 100644 compose.yml create mode 100644 packages/next/.eslintrc.json create mode 100644 packages/next/.gitignore create mode 100644 packages/next/.nvmrc create mode 100644 packages/next/CHECKS create mode 100644 packages/next/Dockerfile.old create mode 100644 packages/next/LICENSE create mode 100644 packages/next/README.md create mode 100644 packages/next/app.json create mode 100644 packages/next/app/about/page.tsx create mode 100644 packages/next/app/api/blogs/route.ts create mode 100644 packages/next/app/api/page.tsx create mode 100644 packages/next/app/api/revalidate/route.ts create mode 100644 packages/next/app/api/service.json/route.ts create mode 100644 packages/next/app/api/v1.json/route.ts create mode 100644 packages/next/app/blog/2021-10-29-the-story-of-futureporn/page.tsx create mode 100644 packages/next/app/blog/page.tsx create mode 100644 packages/next/app/components/archive-progress.tsx create mode 100644 packages/next/app/components/auth.tsx create mode 100644 packages/next/app/components/cal.tsx create mode 100644 packages/next/app/components/contributors.tsx create mode 100644 packages/next/app/components/custom-hls-player.tsx create mode 100644 packages/next/app/components/footer.tsx create mode 100644 packages/next/app/components/funding-goal.tsx create mode 100644 packages/next/app/components/icons/carrd.tsx create mode 100644 packages/next/app/components/icons/chaturbate.tsx create mode 100644 packages/next/app/components/icons/fansly.tsx create mode 100644 packages/next/app/components/icons/linktree.tsx create mode 100644 packages/next/app/components/icons/onlyfans.tsx create mode 100644 packages/next/app/components/icons/pornhub.tsx create mode 100644 packages/next/app/components/icons/throne.tsx create mode 100644 packages/next/app/components/ipfs-cid.tsx create mode 100644 packages/next/app/components/ipfs-logo.tsx create mode 100644 packages/next/app/components/ipfs.tsx create mode 100644 packages/next/app/components/linkable-heading.tsx create mode 100644 packages/next/app/components/localized-date.tsx create mode 100644 packages/next/app/components/navbar.tsx create mode 100644 packages/next/app/components/notification-center.tsx create mode 100644 packages/next/app/components/notifications.tsx create mode 100644 packages/next/app/components/pager.tsx create mode 100644 packages/next/app/components/patrons-list.tsx create mode 100644 packages/next/app/components/sortable-tags.tsx create mode 100644 packages/next/app/components/stream-button.tsx create mode 100644 packages/next/app/components/stream-page.tsx create mode 100644 packages/next/app/components/stream.tsx create mode 100644 packages/next/app/components/streams-calendar.tsx create mode 100644 packages/next/app/components/streams-list.tsx create mode 100644 packages/next/app/components/tag-button.tsx create mode 100644 packages/next/app/components/tag.tsx create mode 100644 packages/next/app/components/tagger.tsx create mode 100644 packages/next/app/components/timestamps-list.tsx create mode 100644 packages/next/app/components/toys.tsx create mode 100644 packages/next/app/components/upload-form.tsx create mode 100644 packages/next/app/components/user-controls.tsx create mode 100644 packages/next/app/components/video-context.tsx create mode 100644 packages/next/app/components/video-interactive.tsx create mode 100644 packages/next/app/components/video-player.tsx create mode 100644 packages/next/app/components/video-source-selector.tsx create mode 100644 packages/next/app/components/vod-card.tsx create mode 100644 packages/next/app/components/vod-nav.tsx create mode 100644 packages/next/app/components/vod-page.tsx create mode 100644 packages/next/app/components/vods-list.tsx create mode 100644 packages/next/app/components/vtuber-button.tsx create mode 100644 packages/next/app/components/vtuber-card.tsx create mode 100644 packages/next/app/connect/patreon/redirect/page.tsx create mode 100644 packages/next/app/faq/page.tsx create mode 100644 packages/next/app/favicon.ico create mode 100644 packages/next/app/feed/feed.json/route.ts create mode 100644 packages/next/app/feed/feed.xml/route.ts create mode 100644 packages/next/app/feed/page.tsx create mode 100644 packages/next/app/feed/rss.xml/route.ts create mode 100644 packages/next/app/goals/page.tsx create mode 100644 packages/next/app/health/page.tsx create mode 100644 packages/next/app/latest-vods/[page]/page.tsx create mode 100644 packages/next/app/latest-vods/page.tsx create mode 100644 packages/next/app/layout.tsx create mode 100644 packages/next/app/lib/b2File.ts create mode 100644 packages/next/app/lib/blog.ts create mode 100644 packages/next/app/lib/constants.ts create mode 100644 packages/next/app/lib/contributors.ts create mode 100644 packages/next/app/lib/dates.ts create mode 100644 packages/next/app/lib/fetch-api.ts create mode 100644 packages/next/app/lib/fetchers.ts create mode 100644 packages/next/app/lib/ipfs.ts create mode 100644 packages/next/app/lib/patreon.ts create mode 100644 packages/next/app/lib/pm.ts create mode 100644 packages/next/app/lib/retry.ts create mode 100644 packages/next/app/lib/rss.ts create mode 100644 packages/next/app/lib/shareRef.ts create mode 100644 packages/next/app/lib/streams.ts create mode 100644 packages/next/app/lib/tag-vod-relations.ts create mode 100644 packages/next/app/lib/tags.ts create mode 100644 packages/next/app/lib/timestamps.ts create mode 100644 packages/next/app/lib/toys.ts create mode 100644 packages/next/app/lib/tweets.ts create mode 100644 packages/next/app/lib/types.ts create mode 100644 packages/next/app/lib/useForwardRef.ts create mode 100644 packages/next/app/lib/users.ts create mode 100644 packages/next/app/lib/vods.ts create mode 100644 packages/next/app/lib/vtubers.ts create mode 100644 packages/next/app/page.tsx create mode 100644 packages/next/app/patrons/page.tsx create mode 100644 packages/next/app/profile/page.tsx create mode 100644 packages/next/app/streams/[cuid]/not-found.tsx create mode 100644 packages/next/app/streams/[cuid]/page.tsx create mode 100644 packages/next/app/streams/page.tsx create mode 100644 packages/next/app/tags/[slug]/page.tsx create mode 100644 packages/next/app/tags/page.tsx create mode 100644 packages/next/app/upload/page.tsx create mode 100644 packages/next/app/upload/page.tsx.old create mode 100644 packages/next/app/uppy.tsx create mode 100644 packages/next/app/vods/[safeDateOrCuid]/page.tsx create mode 100644 packages/next/app/vods/page.tsx create mode 100644 packages/next/app/vt/[slug]/history/page.tsx create mode 100644 packages/next/app/vt/[slug]/not-found.tsx create mode 100644 packages/next/app/vt/[slug]/page.tsx create mode 100644 packages/next/app/vt/[slug]/stream/[safeDate]/page.tsx create mode 100644 packages/next/app/vt/[slug]/streams/page.tsx create mode 100644 packages/next/app/vt/[slug]/toys/[page]/page.tsx create mode 100644 packages/next/app/vt/[slug]/toys/page.tsx create mode 100644 packages/next/app/vt/[slug]/vod/[safeDateOrCuid]/page.tsx create mode 100644 packages/next/app/vt/[slug]/vod/page.tsx create mode 100644 packages/next/app/vt/[slug]/vods/[page]/page.tsx create mode 100644 packages/next/app/vt/[slug]/vods/page.tsx create mode 100644 packages/next/app/vt/page.tsx create mode 100644 packages/next/assets/styles/calendar-heatmap.module.scss create mode 100644 packages/next/assets/styles/cid.module.css create mode 100644 packages/next/assets/styles/fp.module.css create mode 100644 packages/next/assets/styles/global.sass create mode 100644 packages/next/assets/styles/icon.module.css create mode 100644 packages/next/assets/styles/player.module.css create mode 100644 packages/next/assets/svg/README.md create mode 100644 packages/next/assets/svg/carrd.svg create mode 100644 packages/next/assets/svg/chaturbate.svg create mode 100644 packages/next/assets/svg/checkmark.svg create mode 100644 packages/next/assets/svg/fansly.tsx create mode 100644 packages/next/assets/svg/ipfs.svg create mode 100644 packages/next/assets/svg/linktree.svg create mode 100644 packages/next/assets/svg/noun-adult-content-1731184.svg create mode 100644 packages/next/assets/svg/noun-anime-3890912.svg create mode 100644 packages/next/assets/svg/noun-avatar-3546974.svg create mode 100644 packages/next/assets/svg/noun-girl-842331.svg create mode 100644 packages/next/assets/svg/noun-network-1603820.svg create mode 100644 packages/next/assets/svg/onlyfans.svg create mode 100644 packages/next/assets/svg/pornhub.svg create mode 100644 packages/next/assets/svg/throne.svg create mode 100644 packages/next/next.config.js create mode 100644 packages/next/package.json create mode 100644 packages/next/public/futureporn-icon.png create mode 100644 packages/next/public/images/.keep create mode 100644 packages/next/public/images/cj_clippy.jpg create mode 100644 packages/next/public/images/default-thumbnail.webp create mode 100644 packages/next/public/images/projekt-melody.jpg create mode 100644 packages/next/public/images/projektmelody-thumbnail.webp create mode 100644 packages/next/public/images/vercel.svg create mode 100644 packages/next/tsconfig.json create mode 100644 packages/strapi/.dockerignore create mode 100644 packages/strapi/.gitignore create mode 100644 packages/strapi/.nvmrc create mode 100644 packages/strapi/.strapi/client/app.js create mode 100644 packages/strapi/.strapi/client/index.html create mode 100644 packages/strapi/Dockerfile create mode 100644 packages/strapi/README.md create mode 100644 packages/strapi/backup/Dockerfile.1704607848934 create mode 100644 packages/strapi/chisel.sh create mode 100644 packages/strapi/config/admin.js create mode 100644 packages/strapi/config/api.js create mode 100644 packages/strapi/config/database.js create mode 100644 packages/strapi/config/middlewares.js create mode 100644 packages/strapi/config/plugins.js create mode 100644 packages/strapi/config/server.js create mode 100644 packages/strapi/database/daily-backup.sh create mode 100755 packages/strapi/database/devDb.sh create mode 100644 packages/strapi/database/migrations/.gitkeep create mode 100644 packages/strapi/database/migrations/2023-08-01-relate-vods-to-vtubers-part2.js create mode 100644 packages/strapi/database/migrations/2023-08-17-reformat-cdnUrl.js create mode 100644 packages/strapi/database/migrations/2023-08-20-strip-query-string-from-cid.js create mode 100644 packages/strapi/database/migrations/2023-08-30-remove-cloudinary.js create mode 100644 packages/strapi/database/migrations/2023-08-30-toy-image-field-simplify.js create mode 100644 packages/strapi/database/migrations/2023-09-08-change-date-to-string.js create mode 100644 packages/strapi/database/migrations/2023-09-08-drop-toys-image.js create mode 100644 packages/strapi/database/migrations/2023-09-08-drop-vod-videosrc.js create mode 100644 packages/strapi/database/migrations/2023-12-24-add-cuid-to-vods.js create mode 100644 packages/strapi/database/migrations/2023-12-26-add-cuid-to-streams.js create mode 100644 packages/strapi/database/migrations/2023-12-27-relate-vods-to-streams.js create mode 100644 packages/strapi/database/migrations/2023.05.09-video-src-sanity.js.noexec create mode 100644 packages/strapi/database/migrations/2023.05.11T12.32.00.convert-to-video-src-b2.js.noexec create mode 100644 packages/strapi/database/migrations/2023.05.14T00.42.00.000Z.migrate-tags-to-tag-vod-relations.js create mode 100644 packages/strapi/database/migrations/2023.05.15T02.44.00.000Z.drop-vod-tags.js create mode 100644 packages/strapi/database/migrations/2023.05.25-gimme-the-tags.js.noexec create mode 100644 packages/strapi/database/migrations/2023.05.25T20.44.00.000Z.get-the-og-tags.js create mode 100644 packages/strapi/database/migrations/2023.07.17.relate-vods-to-vtubers.js create mode 100644 packages/strapi/database/migrations/2023.07.31.add-b2-file-cdnUrl.js create mode 100644 packages/strapi/database/migrations/2024-01-08-add-streams.js.noexec create mode 100644 packages/strapi/database/migrations/2024-01-14-add-date2-to-streams.js create mode 100644 packages/strapi/database/migrations/2024-01-15-add-platform-to-streams.js create mode 100644 packages/strapi/database/og-tags.json create mode 100644 packages/strapi/favicon.png create mode 100644 packages/strapi/misc/2023-05-26-export-og-tags.js create mode 100644 packages/strapi/misc/generateCuid.js create mode 100644 packages/strapi/package.json create mode 100644 packages/strapi/public/robots.txt create mode 100644 packages/strapi/public/uploads/.gitkeep create mode 100644 packages/strapi/src/admin/app.example.js create mode 100644 packages/strapi/src/admin/webpack.config.example.js create mode 100644 packages/strapi/src/api/.gitkeep create mode 100644 packages/strapi/src/api/b2-file/content-types/b2-file/schema.json create mode 100644 packages/strapi/src/api/b2-file/controllers/b2-file.js create mode 100644 packages/strapi/src/api/b2-file/routes/b2-file.js create mode 100644 packages/strapi/src/api/b2-file/services/b2-file.js create mode 100644 packages/strapi/src/api/contributor/content-types/contributor/schema.json create mode 100644 packages/strapi/src/api/contributor/controllers/contributor.js create mode 100644 packages/strapi/src/api/contributor/routes/contributor.js create mode 100644 packages/strapi/src/api/contributor/services/contributor.js create mode 100644 packages/strapi/src/api/goal/content-types/goal/schema.json create mode 100644 packages/strapi/src/api/goal/controllers/goal.js create mode 100644 packages/strapi/src/api/goal/routes/goal.js create mode 100644 packages/strapi/src/api/goal/services/goal.js create mode 100644 packages/strapi/src/api/gogs/content-types/gogs/schema.json create mode 100644 packages/strapi/src/api/gogs/controllers/gogs.js create mode 100644 packages/strapi/src/api/gogs/routes/gogs.js create mode 100644 packages/strapi/src/api/gogs/services/gogs.js create mode 100644 packages/strapi/src/api/issue/content-types/issue/schema.json create mode 100644 packages/strapi/src/api/issue/controllers/issue.js create mode 100644 packages/strapi/src/api/issue/routes/issue.js create mode 100644 packages/strapi/src/api/issue/services/issue.js create mode 100644 packages/strapi/src/api/mux-asset/content-types/mux-asset/schema.json create mode 100644 packages/strapi/src/api/mux-asset/controllers/mux-asset.js create mode 100644 packages/strapi/src/api/mux-asset/routes/mux-asset.js create mode 100644 packages/strapi/src/api/mux-asset/services/mux-asset.js create mode 100644 packages/strapi/src/api/patreon/content-types/patreon/schema.json create mode 100644 packages/strapi/src/api/patreon/controllers/patreon.js create mode 100644 packages/strapi/src/api/patreon/routes/patreon.js create mode 100644 packages/strapi/src/api/patreon/services/patreon.js create mode 100644 packages/strapi/src/api/profile/controllers/profile.js create mode 100644 packages/strapi/src/api/profile/routes/profile.js create mode 100644 packages/strapi/src/api/profile/services/profile.js create mode 100644 packages/strapi/src/api/stream/content-types/stream/lifecycles.js create mode 100644 packages/strapi/src/api/stream/content-types/stream/schema.json create mode 100644 packages/strapi/src/api/stream/controllers/stream.js create mode 100644 packages/strapi/src/api/stream/routes/stream.js create mode 100644 packages/strapi/src/api/stream/services/stream.js create mode 100644 packages/strapi/src/api/tag-vod-relation/content-types/tag-vod-relation/schema.json create mode 100644 packages/strapi/src/api/tag-vod-relation/controllers/tag-vod-relation.js create mode 100644 packages/strapi/src/api/tag-vod-relation/routes/tag-vod-relation.js create mode 100644 packages/strapi/src/api/tag-vod-relation/services/tag-vod-relation.js create mode 100644 packages/strapi/src/api/tag/content-types/tag/schema.json create mode 100644 packages/strapi/src/api/tag/controllers/tag.js create mode 100644 packages/strapi/src/api/tag/routes/tag.js create mode 100644 packages/strapi/src/api/tag/services/tag.js create mode 100644 packages/strapi/src/api/timestamp/content-types/timestamp/schema.json create mode 100644 packages/strapi/src/api/timestamp/controllers/timestamp.js create mode 100644 packages/strapi/src/api/timestamp/routes/timestamp.js create mode 100644 packages/strapi/src/api/timestamp/services/timestamp.js create mode 100644 packages/strapi/src/api/toy/content-types/toy/schema.json create mode 100644 packages/strapi/src/api/toy/controllers/toy.js create mode 100644 packages/strapi/src/api/toy/routes/toy.js create mode 100644 packages/strapi/src/api/toy/services/toy.js create mode 100644 packages/strapi/src/api/tweet/content-types/tweet/lifecycles.js create mode 100644 packages/strapi/src/api/tweet/content-types/tweet/schema.json create mode 100644 packages/strapi/src/api/tweet/controllers/tweet.js create mode 100644 packages/strapi/src/api/tweet/routes/tweet.js create mode 100644 packages/strapi/src/api/tweet/services/tweet.js create mode 100644 packages/strapi/src/api/user-submitted-content/content-types/user-submitted-content/lifecycles.js create mode 100644 packages/strapi/src/api/user-submitted-content/content-types/user-submitted-content/schema.json create mode 100644 packages/strapi/src/api/user-submitted-content/controllers/user-submitted-content.js create mode 100644 packages/strapi/src/api/user-submitted-content/routes/user-submitted-content.js create mode 100644 packages/strapi/src/api/user-submitted-content/services/user-submitted-content.js create mode 100644 packages/strapi/src/api/vod/content-types/lifecycles.js create mode 100644 packages/strapi/src/api/vod/content-types/vod/schema.json create mode 100644 packages/strapi/src/api/vod/controllers/vod.js create mode 100644 packages/strapi/src/api/vod/routes/vod.js create mode 100644 packages/strapi/src/api/vod/services/vod.js create mode 100644 packages/strapi/src/api/vtuber/content-types/vtuber/lifecycles.js create mode 100644 packages/strapi/src/api/vtuber/content-types/vtuber/schema.json create mode 100644 packages/strapi/src/api/vtuber/controllers/vtuber.js create mode 100644 packages/strapi/src/api/vtuber/routes/vtuber.js create mode 100644 packages/strapi/src/api/vtuber/services/vtuber.js create mode 100644 packages/strapi/src/extensions/.gitkeep create mode 100644 packages/strapi/src/extensions/users-permissions/.eslintignore create mode 100644 packages/strapi/src/extensions/users-permissions/.eslintrc.js create mode 100644 packages/strapi/src/extensions/users-permissions/LICENSE create mode 100644 packages/strapi/src/extensions/users-permissions/README.md create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/components/BoundRoute/getMethodColor.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/components/BoundRoute/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/components/FormModal/Input/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/components/FormModal/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/PermissionRow/CheckboxWrapper.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/PermissionRow/SubCategory.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/PermissionRow/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/init.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/reducer.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/components/Policies/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/components/UsersPermissions/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/components/UsersPermissions/init.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/components/UsersPermissions/reducer.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/contexts/UsersPermissionsContext/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/hooks/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/hooks/useFetchRole/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/hooks/useFetchRole/reducer.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/hooks/useForm/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/hooks/useForm/reducer.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/hooks/usePlugins.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/hooks/useRolesList/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/hooks/useRolesList/init.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/hooks/useRolesList/reducer.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/utils/api.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/utils/layout.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/utils/schema.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/EmailTemplates/components/EmailForm.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/EmailTemplates/components/EmailTable.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/EmailTemplates/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/EmailTemplates/utils/api.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/EmailTemplates/utils/schema.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/Providers/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/Providers/reducer.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/Providers/utils/api.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/Providers/utils/createProvidersArray.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/Providers/utils/forms.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/Roles/CreatePage.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/Roles/EditPage.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/Roles/ListPage/components/TableBody.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/Roles/ListPage/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/Roles/ListPage/utils/api.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/Roles/ProtectedCreatePage.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/Roles/ProtectedEditPage.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/Roles/ProtectedListPage.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/Roles/constants.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pages/Roles/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/permissions.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/pluginId.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/ar.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/cs.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/de.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/dk.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/en.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/es.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/fr.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/id.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/it.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/ja.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/ko.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/ms.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/nl.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/pl.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/pt-BR.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/pt.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/ru.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/sk.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/sv.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/th.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/tr.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/uk.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/vi.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/zh-Hans.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/translations/zh.json create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/utils/cleanPermissions.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/utils/formatPluginName.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/utils/formatPolicies.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/utils/getRequestURL.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/utils/getTrad.js create mode 100644 packages/strapi/src/extensions/users-permissions/admin/src/utils/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/content-types/user/schema.json create mode 100644 packages/strapi/src/extensions/users-permissions/documentation/content-api.yaml create mode 100644 packages/strapi/src/extensions/users-permissions/jest.config.front.js create mode 100644 packages/strapi/src/extensions/users-permissions/package.json create mode 100644 packages/strapi/src/extensions/users-permissions/server/bootstrap/grant-config.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/bootstrap/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/bootstrap/users-permissions-actions.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/config.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/content-types/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/content-types/permission/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/content-types/role/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/content-types/user/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/content-types/user/schema-config.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/controllers/auth.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/controllers/content-manager-user.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/controllers/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/controllers/permissions.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/controllers/role.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/controllers/settings.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/controllers/user.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/controllers/validation/auth.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/controllers/validation/email-template.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/controllers/validation/user.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/mutations/auth/change-password.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/mutations/auth/email-confirmation.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/mutations/auth/forgot-password.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/mutations/auth/login.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/mutations/auth/register.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/mutations/auth/reset-password.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/mutations/crud/role/create-role.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/mutations/crud/role/delete-role.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/mutations/crud/role/update-role.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/mutations/crud/user/create-user.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/mutations/crud/user/delete-user.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/mutations/crud/user/update-user.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/mutations/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/queries/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/queries/me.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/resolvers-configs.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/types/create-role-payload.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/types/delete-role-payload.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/types/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/types/login-input.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/types/login-payload.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/types/me-role.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/types/me.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/types/password-payload.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/types/register-input.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/types/update-role-payload.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/graphql/utils.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/middlewares/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/middlewares/rateLimit.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/register.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/routes/admin/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/routes/admin/permissions.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/routes/admin/role.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/routes/admin/settings.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/routes/content-api/auth.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/routes/content-api/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/routes/content-api/permissions.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/routes/content-api/role.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/routes/content-api/user.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/routes/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/services/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/services/jwt.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/services/permission.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/services/providers-registry.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/services/providers.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/services/role.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/services/user.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/services/users-permissions.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/strategies/users-permissions.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/utils/index.d.ts create mode 100644 packages/strapi/src/extensions/users-permissions/server/utils/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/utils/sanitize/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/utils/sanitize/sanitizers.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/utils/sanitize/visitors/index.js create mode 100644 packages/strapi/src/extensions/users-permissions/server/utils/sanitize/visitors/remove-user-relation-from-role-entities.js create mode 100644 packages/strapi/src/extensions/users-permissions/strapi-admin.js create mode 100644 packages/strapi/src/extensions/users-permissions/strapi-server.js create mode 100644 packages/strapi/src/index.js create mode 100644 packages/strapi/src/policies/updateOwnerOnly.js create mode 100644 packages/strapi/yarn.lock create mode 100644 packages/uppy/.env.old create mode 100644 packages/uppy/.env.production create mode 100644 packages/uppy/.gitignore create mode 100644 packages/uppy/Dockerfile create mode 100644 packages/uppy/README.md create mode 100755 packages/uppy/apply-backblaze-cors-rules.sh create mode 100644 packages/uppy/index.js create mode 100644 packages/uppy/package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7da9ff0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +Dockerfile +.dockerignore +node_modules +npm-debug.log +README.md +.next +.git +LICENSE +.nvmrc +CHECKS +app.json +.env* +compose/ +docker-compose.* +.vscode \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..04a0ccf --- /dev/null +++ b/.gitignore @@ -0,0 +1,148 @@ +compose/ +.env + +# Created by https://www.toptal.com/developers/gitignore/api/node +# Edit at https://www.toptal.com/developers/gitignore?templates=node + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +# End of https://www.toptal.com/developers/gitignore/api/node + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..aac7ccf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM node:20-slim AS base +ENV NEXT_TELEMETRY_DISABLED 1 +RUN corepack enable + +FROM base AS build +WORKDIR /usr/src/fp-monorepo +RUN mkdir /usr/src/next +COPY ./pnpm-lock.yaml ./ +COPY ./pnpm-workspace.yaml ./ +COPY ./packages/next/package.json ./packages/next/ +RUN --mount=type=cache,id=pnpm-store,target=/root/.pnpm-store pnpm install +COPY . . +RUN pnpm deploy --filter=fp-next /usr/src/next + +FROM base AS dev +WORKDIR /app +COPY --from=build /usr/src/next /app +CMD ["pnpm", "run", "dev"] + diff --git a/compose.prod.yml b/compose.prod.yml new file mode 100644 index 0000000..a1c6841 --- /dev/null +++ b/compose.prod.yml @@ -0,0 +1,103 @@ +version: '3.4' + + +services: + + link2cid: + container_name: fp-link2cid + image: insanity54/link2cid:latest + ports: + - "3939:3939" + environment: + API_KEY: ${LINK2CID_API_KEY} + IPFS_URL: "http://ipfs0:5001" + + ipfs0: + container_name: fp-ipfs0 + image: ipfs/kubo:release + ports: + - "5001:5001" + volumes: + - ./packages/ipfs0:/data/ipfs + + cluster0: + container_name: fp-cluster0 + image: ipfs/ipfs-cluster:latest + depends_on: + - ipfs0 + environment: + CLUSTER_PEERNAME: cluster0 + CLUSTER_SECRET: ${CLUSTER_SECRET} # From shell variable if set + CLUSTER_IPFSHTTP_NODEMULTIADDRESS: /dns4/ipfs0/tcp/5001 + CLUSTER_CRDT_TRUSTEDPEERS: '*' # Trust all peers in Cluster + CLUSTER_RESTAPI_HTTPLISTENMULTIADDRESS: /ip4/0.0.0.0/tcp/9094 # Expose API + CLUSTER_RESTAPI_BASICAUTHCREDENTIALS: ${CLUSTER_RESTAPI_BASICAUTHCREDENTIALS} + CLUSTER_MONITORPINGINTERVAL: 2s # Speed up peer discovery + ports: + - "127.0.0.1:9094:9094" + volumes: + - ./packages/cluster0:/data/ipfs-cluster + + strapi: + container_name: fp-strapi + image: elestio/strapi-development + depends_on: + - db + environment: + ADMIN_PASSWORD: ${STRAPI_ADMIN_PASSWORD} + ADMIN_EMAIL: ${STRAPI_ADMIN_EMAIL} + BASE_URL: ${STRAPI_BASE_URL} + SMTP_HOST: 172.17.0.1 + SMTP_PORT: 25 + SMTP_AUTH_STRATEGY: NONE + SMTP_FROM_EMAIL: sender@email.com + DATABASE_CLIENT: postgres + DATABASE_PORT: ${DATABASE_PORT} + DATABASE_NAME: ${DATABASE_NAME} + DATABASE_USERNAME: ${DATABASE_USERNAME} + DATABASE_PASSWORD: ${DATABASE_PASSWORD} + JWT_SECRET: ${STRAPI_JWT_SECRET} + ADMIN_JWT_SECRET: ${STRAPI_ADMIN_JWT_SECRET} + APP_KEYS: ${STRAPI_APP_KEYS} + NODE_ENV: development + DATABASE_HOST: db + API_TOKEN_SALT: ${STRAPI_API_TOKEN_SALT} + TRANSFER_TOKEN_SALT: ${STRAPI_TRANSFER_TOKEN_SALT} + ports: + - "1337:1337" + volumes: + - ./packages/strapi/config:/opt/app/config + - ./packages/strapi/src:/opt/app/src + # - ./packages/strapi/package.json:/opt/package.json + # - ./packages/strapi/yarn.lock:/opt/yarn.lock + - ./packages/strapi/.env:/opt/app/.env + - ./packages/strapi/public/uploads:/opt/app/public/uploads + # - ./packages/strapi/entrypoint.sh:/opt/app/entrypoint.sh + + next: + container_name: fp-next + build: + context: ./packages/next + dockerfile: Dockerfile + environment: + REVALIDATION_TOKEN: ${NEXT_REVALIDATION_TOKEN} + NODE_ENV: production + ports: + - "3000:3000" + volumes: + - ./packages/next/ + + + db: + container_name: fp-db + image: postgres:latest + restart: always + environment: + POSTGRES_DB: ${DATABASE_NAME} + POSTGRES_USER: ${DATABASE_USERNAME} + POSTGRES_PASSWORD: ${DATABASE_PASSWORD} + PGDATA: /var/lib/postgresql/data + volumes: + - ./packages/db/pgdata:/var/lib/postgresql/data + ports: + - "5433:5432" \ No newline at end of file diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..f220045 --- /dev/null +++ b/compose.yml @@ -0,0 +1,190 @@ +version: '3.4' + + +services: + + chisel: + container_name: fp-chisel + image: jpillora/chisel + ports: + - "9312:9312" + restart: on-failure + command: "client --auth=${CHISEL_AUTH} ${CHISEL_SERVER} R:8899:cluster0:9094 R:8901:link2cid:3939 R:8900:strapi:1337 R:8902:next:3000 R:8903:uppy:3020" + + link2cid: + container_name: fp-link2cid + restart: on-failure + image: insanity54/link2cid:latest + ports: + - "3939:3939" + environment: + API_KEY: ${LINK2CID_API_KEY} + IPFS_URL: "http://ipfs0:5001" + + ipfs0: + container_name: fp-ipfs0 + restart: on-failure + image: ipfs/kubo:release + ports: + - "5001:5001" + volumes: + - ./compose/ipfs0:/data/ipfs + + cluster0: + container_name: fp-cluster0 + image: ipfs/ipfs-cluster:latest + restart: on-failure + depends_on: + - ipfs0 + environment: + CLUSTER_PEERNAME: cluster0 + CLUSTER_SECRET: ${CLUSTER_SECRET} # From shell variable if set + CLUSTER_IPFSHTTP_NODEMULTIADDRESS: /dns4/ipfs0/tcp/5001 + CLUSTER_CRDT_TRUSTEDPEERS: '*' # Trust all peers in Cluster + CLUSTER_RESTAPI_HTTPLISTENMULTIADDRESS: /ip4/0.0.0.0/tcp/9094 # Expose API + CLUSTER_RESTAPI_BASICAUTHCREDENTIALS: ${CLUSTER_RESTAPI_BASICAUTHCREDENTIALS} + CLUSTER_MONITORPINGINTERVAL: 2s # Speed up peer discovery + ports: + - "127.0.0.1:9094:9094" + volumes: + - ./compose/cluster0:/data/ipfs-cluster + + strapi: + container_name: fp-strapi + image: fp-strapi:14 + build: + context: ./packages/strapi + dockerfile: Dockerfile + restart: on-failure + depends_on: + - db + # env_file: ./packages/strapi/.env + environment: + # ADMIN_PASSWORD: ${STRAPI_ADMIN_PASSWORD} + # ADMIN_EMAIL: ${STRAPI_ADMIN_EMAIL} + BASE_URL: ${STRAPI_BASE_URL} + SMTP_HOST: 172.17.0.1 + SMTP_PORT: 25 + SMTP_AUTH_STRATEGY: NONE + SMTP_FROM_EMAIL: sender@example.com + SENDGRID_API_KEY: ${SENDGRID_API_KEY} + DATABASE_CLIENT: postgres + DATABASE_HOST: db + DATABASE_PORT: ${POSTGRES_PORT} + DATABASE_NAME: ${POSTGRES_DB} + DATABASE_USERNAME: ${POSTGRES_USER} + DATABASE_PASSWORD: ${POSTGRES_PASSWORD} + JWT_SECRET: ${STRAPI_JWT_SECRET} + ADMIN_JWT_SECRET: ${STRAPI_ADMIN_JWT_SECRET} + APP_KEYS: ${STRAPI_APP_KEYS} + NODE_ENV: ${NODE_ENV} + API_TOKEN_SALT: ${STRAPI_API_TOKEN_SALT} + TRANSFER_TOKEN_SALT: ${STRAPI_TRANSFER_TOKEN_SALT} + MUX_SIGNING_KEY_PRIVATE_KEY: ${MUX_SIGNING_KEY_PRIVATE_KEY} + MUX_SIGNING_KEY_ID: ${MUX_SIGNING_KEY_ID} + MUX_PLAYBACK_RESTRICTION_ID: ${MUX_PLAYBACK_RESTRICTION_ID} + STRAPI_URL: ${STRAPI_URL} + CDN_BUCKET_URL: ${CDN_BUCKET_URL} + CDN_BUCKET_USC_URL: ${CDN_BUCKET_USC_URL} + S3_USC_BUCKET_KEY_ID: ${S3_USC_BUCKET_KEY_ID} + S3_USC_BUCKET_APPLICATION_KEY: ${S3_USC_BUCKET_APPLICATION_KEY} + S3_USC_BUCKET_NAME: ${S3_USC_BUCKET_NAME} + S3_USC_BUCKET_ENDPOINT: ${S3_USC_BUCKET_ENDPOINT} + S3_USC_BUCKET_REGION: ${S3_USC_BUCKET_REGION} + AWS_ACCESS_KEY_ID: ${S3_USC_BUCKET_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${S3_USC_BUCKET_APPLICATION_KEY} + + ports: + - "1337:1337" + volumes: + - ./packages/strapi/config:/opt/app/config + - ./packages/strapi/src:/opt/app/src + - ./packages/strapi/database:/opt/app/database + - ./packages/strapi/public/uploads:/opt/app/public/uploads + - ./packages/strapi/package.json:/opt/app/package.json + - ./packages/strapi/yarn.lock:/opt/app/yarn.lock + # - ./packages/strapi/.env:/opt/app/.env + # - ./packages/strapi/entrypoint.sh:/opt/app/entrypoint.sh + + next: + container_name: fp-next + build: + context: . + dockerfile: Dockerfile + target: dev + restart: on-failure + environment: + REVALIDATION_TOKEN: ${NEXT_REVALIDATION_TOKEN} + NODE_ENV: development + NEXT_PUBLIC_STRAPI_URL: ${NEXT_PUBLIC_STRAPI_URL} + NEXT_PUBLIC_UPPY_COMPANION_URL: ${NEXT_PUBLIC_UPPY_COMPANION_URL} + NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL} + ports: + - "3000:3000" + volumes: + # - /app/node_modules + # - /app/.next + # - /app/.pnpm-store + - ./packages/next/app:/app/app + + + db: + container_name: fp-db + image: postgres:16 + restart: on-failure + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + PGDATA: /var/lib/postgresql/data + PGPORT: ${POSTGRES_PORT} + volumes: + - ./compose/db/pgdata:/var/lib/postgresql/data + ports: + - "15432:15432" + + pgadmin: + container_name: fp-pgadmin + image: dpage/pgadmin4:8 + restart: on-failure + environment: + PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL} + PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD} + PGADMIN_DISABLE_POSTFIX: yessir + GUNICORN_ACCESS_LOGFILE: /tmp/pgadmin-gunicorn-access.log # this makes console output less noisy + ports: + - "5050:80" + + + uppy: + container_name: fp-uppy + build: + context: . + dockerfile: ./packages/uppy/Dockerfile + target: run + restart: on-failure + environment: + SESSION_SECRET: ${UPPY_SESSION_SECRET} + PORT: ${UPPY_PORT} + FILEPATH: ${UPPY_FILEPATH} + NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL} + HOST: ${UPPY_HOST} + UPLOAD_URLS: ${UPPY_UPLOAD_URLS} + SECRET: ${UPPY_SECRET} + SERVER_BASE_URL: ${UPPY_SERVER_BASE_URL} + B2_ENDPOINT: ${UPPY_B2_ENDPOINT} + B2_BUCKET: ${UPPY_B2_BUCKET} + B2_SECRET: ${UPPY_B2_SECRET} + B2_KEY: ${UPPY_B2_KEY} + B2_REGION: ${UPPY_B2_REGION} + DRIVE_KEY: ${UPPY_DRIVE_KEY} + DRIVE_SECRET: ${UPPY_DRIVE_SECRET} + DROPBOX_KEY: ${UPPY_DROPBOX_KEY} + DROPBOX_SECRET: ${UPPY_DROPBOX_SECRET} + JWT_SECRET: ${STRAPI_JWT_SECRET} # we use strapi's JWT secret so we can verify that uploads are from account holders + STRAPI_API_KEY: ${UPPY_STRAPI_API_KEY} + STRAPI_URL: ${UPPY_STRAPI_URL} + ports: + - "3020:3020" + volumes: + - ./packages/uppy/index.js:/app/index.js \ No newline at end of file diff --git a/packages/next/.eslintrc.json b/packages/next/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/packages/next/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/packages/next/.gitignore b/packages/next/.gitignore new file mode 100644 index 0000000..473707c --- /dev/null +++ b/packages/next/.gitignore @@ -0,0 +1,47 @@ +# Created by https://www.toptal.com/developers/gitignore/api/nextjs +# Edit at https://www.toptal.com/developers/gitignore?templates=nextjs + + +.vscode/ + +.env +.env.* +dist/ + +### NextJS ### +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# End of https://www.toptal.com/developers/gitignore/api/nextjs diff --git a/packages/next/.nvmrc b/packages/next/.nvmrc new file mode 100644 index 0000000..9de2256 --- /dev/null +++ b/packages/next/.nvmrc @@ -0,0 +1 @@ +lts/iron diff --git a/packages/next/CHECKS b/packages/next/CHECKS new file mode 100644 index 0000000..6731352 --- /dev/null +++ b/packages/next/CHECKS @@ -0,0 +1 @@ +/ futureporn.net \ No newline at end of file diff --git a/packages/next/Dockerfile.old b/packages/next/Dockerfile.old new file mode 100644 index 0000000..98094ed --- /dev/null +++ b/packages/next/Dockerfile.old @@ -0,0 +1,35 @@ +## @greetz https://medium.com/@elifront/best-next-js-docker-compose-hot-reload-production-ready-docker-setup-28a9125ba1dc + +FROM node:20-slim AS base +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable +RUN apt-get update && apt-get install -y -qq dumb-init +COPY . /app +WORKDIR /app + + +FROM base AS deps +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile + + +FROM base AS taco +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + + +FROM deps AS build +ENV NEXT_TELEMETRY_DISABLED 1 +RUN pnpm run -r build + + +FROM deps AS runner +ENV NEXT_TELEMETRY_DISABLED 1 +WORKDIR /app +COPY --from=build /usr/src/app/public ./public +COPY --from=build /usr/src/app/.next/standalone ./ +COPY --from=build /usr/src/app/.next/static ./.next/static +EXPOSE 3000 +ENV HOSTNAME="0.0.0.0" +CMD [ "dumb-init", "node", "server.js" ] diff --git a/packages/next/LICENSE b/packages/next/LICENSE new file mode 100644 index 0000000..7c53cea --- /dev/null +++ b/packages/next/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Rogier van den Berg + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/next/README.md b/packages/next/README.md new file mode 100644 index 0000000..1bd2474 --- /dev/null +++ b/packages/next/README.md @@ -0,0 +1,27 @@ +# futureporn-next + +## Dev notes + +When adding a new module via pnpm, docker compose needs to be restarted or something. I'm not sure the exact steps just yet, but I think it's something like the following. + +``` +pnpm add @uppy/react +docker compose build next +``` + +> fp-next | Module not found: Can't resolve '@uppy/react' + +hmm... It looks like I'm missing something. Is the new package not getting into the container? Maybe it's something to do with the pnpm cache? + +Must we build without cache? + + docker compose build --no-cache next; docker compose up + +YES. that solved the issue. + +However, it's really slow to purge cache and download all packages once again. Is there a way we can speed this up? + +* make it work +* make it right +* make it fast + diff --git a/packages/next/app.json b/packages/next/app.json new file mode 100644 index 0000000..31825ab --- /dev/null +++ b/packages/next/app.json @@ -0,0 +1,14 @@ +{ + "healthchecks": { + "web": [ + { + "type": "startup", + "name": "web check", + "description": "Checking for expecting string at /api", + "path": "/api", + "content": "Application Programmable Interface", + "attempts": 3 + } + ] + } +} \ No newline at end of file diff --git a/packages/next/app/about/page.tsx b/packages/next/app/about/page.tsx new file mode 100644 index 0000000..87de407 --- /dev/null +++ b/packages/next/app/about/page.tsx @@ -0,0 +1,64 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons"; +import Link from 'next/link'; +// import { getProgress } from '../lib/vods' + +export default async function Page() { + // const { complete, total } = await getProgress('projektmelody') + + return ( + <> +
+
+ +
+ +

About

+
+

Futureporn is a fanmade public archive of NSFW R18 vtuber livestreams.

+
+ +

Mission

+
+ +

It's a lofty goal, but Futureporn aims to become the Galaxy's best VTuber hentai site.

+
+ +

How do we get there?

+ +
+

1. Solve the viewer's common problems

+ +

Viewers want to watch livestream VODs on their own time. Futureporn collects vods from public streams, and caches them for later viewing.

+ +

Viewers want to find content that interests them. Futureporn enables vod tagging for easy browsing.

+
+ +
+

2. Solve the streamer's common problems

+ +

Platforms like PH are not rising to the needs of VTubers. Instead of offering support and resources, they restrict and ban top talent.

+ +

Futureporn is different, embracing the medium and leveraging emerging technologies to amplify VTuber success.

+
+ +
+

3. Scale beyond Earth

+ +

Piggybacking on IPFS' content-addressable capabilities and potential to end 404s, VODs preserved here can withstand the test of time, and eventually persist off-world.

+
+ +
+
+
+

Futureporn needs financial support to continue improving. If you enjoy this website, please consider becoming a patron.

+
+
+
+ +
+
+
+ + ) +} diff --git a/packages/next/app/api/blogs/route.ts b/packages/next/app/api/blogs/route.ts new file mode 100644 index 0000000..dca5232 --- /dev/null +++ b/packages/next/app/api/blogs/route.ts @@ -0,0 +1,10 @@ +import { NextResponse } from 'next/server' + +export async function GET() { + const res = await fetch('https://dummyjson.com/posts', { + next: { revalidate: 60 }, + }); + const data = await res.json(); + + return NextResponse.json(data); +} diff --git a/packages/next/app/api/page.tsx b/packages/next/app/api/page.tsx new file mode 100644 index 0000000..f1a47d8 --- /dev/null +++ b/packages/next/app/api/page.tsx @@ -0,0 +1,118 @@ +'use client'; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons"; +import Link from 'next/link' +import { Highlight, themes } from "prism-react-renderer"; + +const bootstrapScript = `#!/bin/bash + +## bootstrap.sh +## tested on Ubuntu 22.04 + +## install dependencies +cd +apt install -y screen + +## Open necessary firewall ports +ufw allow 9096/tcp +ufw allow 9094/tcp +ufw allow 4001/tcp +ufw allow 4001/udp + +## Download kubo +wget 'https://dist.ipfs.tech/kubo/v0.24.0/kubo_v0.24.0_linux-amd64.tar.gz' +tar xvzf ./kubo_v0.24.0_linux-amd64.tar.gz +chmod +x ./kubo/install.sh +./kubo/install.sh + +## Download ipfs-cluster-follow +wget 'https://dist.ipfs.tech/ipfs-cluster-follow/v1.0.7/ipfs-cluster-follow_v1.0.7_linux-amd64.tar.gz' +tar xvzf ./ipfs-cluster-follow_v1.0.7_linux-amd64.tar.gz +chmod +x ./ipfs-cluster-follow/ipfs-cluster-follow +mv ./ipfs-cluster-follow/ipfs-cluster-follow /usr/local/bin/ + +## initialize ipfs +ipfs init + +## run ipfs in a screen session +screen -d -m ipfs daemon + +## run ipfs-cluster-follow +CLUSTER_PEERNAME="my-cluster-peer-name" ipfs-cluster-follow futureporn.net run --init https://futureporn.net/api/service.json +` + +export default function Page() { + return ( +
+
+

Futureporn API

+

Futureporn Application Programmable Interface (API) for developers and power users

+
+
+
+
+

RSS Feed

+

Keep up to date with new VODs using Real Simple Syndication (RSS).

+ +

Don't have a RSS reader? Futureporn recommends Fraidycat

+ +
+

ATOM

+

RSS

+

JSON

+
+
+
+ +
+
+

Data API

+

The Data API contains all the data served by this website in JSON format, including IPFS Content IDs (CID), VOD titles, dates, and stream announcement links.

+

Futureporn API Version 1

+
+
+ +
+
+

IPFS Cluster Template

+

The IPFS Cluster Template allows other IPFS cluster instances to join the Futureporn.net IPFS cluster as a follower peer . Cluster peers automatically pin (replicate) the IPFS content listed on this website.

+ +

Basic instructions are as follows

+

1. Download & install both kubo and ipfs-cluster-follow onto your server.

+

2. Initialize your ipfs repo & start the ipfs daemon

+

3. Join the cluster using ipfs-cluster-follow

+ +

Below is an example bash script to get everything you need to run an IPFS follower peer. This is only an example and may need tweaks to run in your environment.

+ + + {({ className, style, tokens, getLineProps, getTokenProps }) => ( +
+                                        {tokens.map((line, i) => (
+                                            
+ {line.map((token, key) => ( + + ))} +
+ ))} +
+ )} +
+ + + + + +

Futureporn IPFS Cluster Template (service.json)

+
+
+
+ + +
+
+ ) +} \ No newline at end of file diff --git a/packages/next/app/api/revalidate/route.ts b/packages/next/app/api/revalidate/route.ts new file mode 100644 index 0000000..bad85ce --- /dev/null +++ b/packages/next/app/api/revalidate/route.ts @@ -0,0 +1,26 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { revalidateTag } from 'next/cache'; + +export const dynamic = 'force-dynamic'; + +export async function GET(request: NextRequest) { + const token = request.nextUrl.searchParams.get('token') + const tag = request.nextUrl.searchParams.get('tag') + + + + if (!token) { + return NextResponse.json({ message: 'Missing token param' }, { status: 400}) + } + + if (!tag) { + return NextResponse.json({ message: 'Missing tag param' }, { status: 400 }) + } + + if (token !== process.env.REVALIDATION_TOKEN) { + return NextResponse.json({ message: 'Invalid token' }, { status: 401 }) + } + + revalidateTag(tag) + return NextResponse.json({ revalidated: true, now: Date.now() }) +} \ No newline at end of file diff --git a/packages/next/app/api/service.json/route.ts b/packages/next/app/api/service.json/route.ts new file mode 100644 index 0000000..9aed096 --- /dev/null +++ b/packages/next/app/api/service.json/route.ts @@ -0,0 +1,139 @@ +import { NextResponse } from 'next/server' + + +const serviceConfig = { + "cluster": { + "peername": "replace-this-with-a-super-cool-peer-name", + "secret": "3acade7f761c91f5fe3d34c4f4d15a17f817bc3463ab4395958f302b222a023b", + "leave_on_shutdown": false, + "listen_multiaddress": [ + "/ip4/0.0.0.0/tcp/9096" + ], + "connection_manager": { + "high_water": 400, + "low_water": 100, + "grace_period": "2m0s" + }, + "dial_peer_timeout": "3s", + "state_sync_interval": "10m", + "pin_recover_interval": "12m", + "ipfs_sync_interval": "130s", + "replication_factor_min": -1, + "replication_factor_max": -1, + "monitor_ping_interval": "30s", + "peer_watch_interval": "10s", + "mdns_interval": "10s", + "disable_repinning": true, + "follower_mode": true, + "peer_addresses": [ + "/dns4/cluster.sbtp.xyz/tcp/9096/p2p/12D3KooWJmCsFadow1UvqAqCGtuKpqrS3puyPUYujJj4dRRCTfXf" + ] + }, + "consensus": { + "crdt": { + "cluster_name": "futureporn.net", + "trusted_peers": [ + "12D3KooWJmCsFadow1UvqAqCGtuKpqrS3puyPUYujJj4dRRCTfXf" + ], + "rebroadcast_interval": "1m", + "peerset_metric": "ping", + "batching": { + "max_batch_size": 0, + "max_batch_age": "0s", + "max_queue_size": 50000 + } + } + }, + "ipfs_connector": { + "ipfshttp": { + "node_multiaddress": "/ip4/127.0.0.1/tcp/5001", + "connect_swarms_delay": "30s", + "ipfs_request_timeout": "5m", + "repogc_timeout": "24h", + "pin_timeout": "3m", + "unpin_timeout": "3h", + "unpin_disable": false + } + }, + "pin_tracker": { + "stateless": { + "max_pin_queue_size": 1000000, + "concurrent_pins": 8, + "priority_pin_max_age" : "24h", + "priority_pin_max_retries" : 5 + } + }, + "monitor": { + "pubsubmon": { + "check_interval": "15s", + "failure_threshold": 3 + } + }, + "informer": { + "disk": { + "metric_ttl": "5m", + "metric_type": "freespace" + }, + "tags": { + "metric_ttl": "30s", + "tags": {} + } + }, + "allocator": { + "balanced": { + "allocate_by": ["freespace"] + } + }, + "observations": { + "metrics": { + "enable_stats": false, + "prometheus_endpoint": "/ip4/0.0.0.0/tcp/8888", + "reporting_interval": "2s" + }, + "tracing": { + "enable_tracing": false, + "jaeger_agent_endpoint": "/ip4/0.0.0.0/udp/6831", + "sampling_prob": 0.3, + "service_name": "cluster-daemon" + } + }, + "datastore": { + "badger": { + "gc_discard_ratio": 0.2, + "gc_interval": "15m0s", + "gc_sleep": "10s", + "badger_options": { + "dir": "", + "value_dir": "", + "sync_writes": true, + "table_loading_mode": 0, + "value_log_loading_mode": 0, + "num_versions_to_keep": 1, + "max_table_size": 67108864, + "level_size_multiplier": 10, + "max_levels": 7, + "value_threshold": 32, + "num_memtables": 5, + "num_level_zero_tables": 5, + "num_level_zero_tables_stall": 10, + "level_one_size": 268435456, + "value_log_file_size": 1073741823, + "value_log_max_entries": 1000000, + "num_compactors": 2, + "compact_l_0_on_close": true, + "read_only": false, + "truncate": false + } + } + } + } + +export const dynamic = 'force-dynamic' +export async function GET() { + const options = { + headers: { + "Content-Type": "application/json", + } + }; + return new NextResponse(JSON.stringify(serviceConfig), options); +} \ No newline at end of file diff --git a/packages/next/app/api/v1.json/route.ts b/packages/next/app/api/v1.json/route.ts new file mode 100644 index 0000000..4433dc1 --- /dev/null +++ b/packages/next/app/api/v1.json/route.ts @@ -0,0 +1,91 @@ + +import { getVodTitle } from '@/components/vod-page'; +import { getUrl, getAllVods } from "@/lib/vods" +import { IVod } from "@/lib/vods" + + +/* + * this is a legacy format + * + * for API version 1. + * + * @deprecated + */ +interface IVod1 { + title: string; + videoSrcHash: string; + video720Hash: string; + video480Hash: string; + video360Hash: string; + video240Hash: string; + thinHash: string; + thiccHash: string; + announceTitle: string; + announceUrl: string; + date: string; + note: string; + url: string; +} + +interface IAPI1 { + vods: IVod1[] +} + + +export async function GET(): Promise { + try { + const vodsRaw = await getAllVods(); + if (!vodsRaw) { + const options = { + headers: { + "Content-Type": "application/json", + }, + status: 500, + }; + return new Response('{}', options); + } + + const vods: IVod1[] = vodsRaw.map((v: IVod): IVod1 => ({ + title: getVodTitle(v), + videoSrcHash: v.attributes.videoSrcHash, + video720Hash: '', + video480Hash: '', + video360Hash: '', + video240Hash: v.attributes.video240Hash, + thinHash: '', + thiccHash: '', + announceTitle: v.attributes.announceTitle, + announceUrl: v.attributes.announceUrl, + date: v.attributes.date2, + note: v.attributes.note || '', + url: getUrl(v, v.attributes.vtuber.data.attributes.slug, v.attributes.date2), + })); + + const response = { + vods: vods, + }; + + const options = { + headers: { + "Content-Type": "application/json", + }, + }; + + return new Response(JSON.stringify(response), options); + } catch (error) { + console.error("Error fetching VODs:", error); + + const errorResponse = { + error: "An error occurred while fetching VODs", + }; + + const options = { + headers: { + "Content-Type": "application/json", + }, + status: 500, + }; + + return new Response(JSON.stringify(errorResponse), options); + } +} \ No newline at end of file diff --git a/packages/next/app/blog/2021-10-29-the-story-of-futureporn/page.tsx b/packages/next/app/blog/2021-10-29-the-story-of-futureporn/page.tsx new file mode 100644 index 0000000..a91a0bf --- /dev/null +++ b/packages/next/app/blog/2021-10-29-the-story-of-futureporn/page.tsx @@ -0,0 +1,54 @@ + +import Link from "next/link" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons" + +export default async function Page() { + + + + return ( +
+
+ +
+ + + + +

The Story of Futureporn

+ +

2020 was a busy time for me. I started a small business, attended lots of support group meetings, and rode my bicycle more than ever before. I often found myself away from home during times when Melody was streaming on Chaturbate.

+ +

You probably know that unlike other video streaming platforms, Chaturbate doesn’t store any VODs. When I missed a stream, I felt sad. I felt like I had missed out and there’s no way I’d ever find out what happened.

+ +

I’m pretty handy with computer software. Creating programs and websites has been my biggest passion for my entire life. In order to never miss a ProjektMelody livestream again, I resolved to create some software that would automatically record Melody’s Chaturbate streams.

+ +

I put the project on hold for a few months, because I didn’t think I could make a website that could handle the traffic that the Science Team would generate.

+ +

I couldn’t shake the idea, though. I wanted Futureporn to exist no matter what!

+ +

I’ve been working on this project off and on for about a year and a half. It’s gone through several iterations, and each iteration has taught me something new. Right now, the website is usable for finding and downloading ProjektMelody Chaturbate VODs. Every VOD has a link to Melody’s tweet which originally announced the stream, and a title/description derived from said tweet. I have archived all of her known Chaturbate streams.

+ +

The project has evolved over time. Originally, I wanted to have a place to go when I missed one of Melody’s livestreams. Now, the project is becoming a sort of a time capsule. We’ve all seen how Melody has been de-platformed a half dozen times, and I’ve taken this to heart. Platforms are a problem for data preservation! This is one of the reasons for why I chose to use the Inter-Planetary File System (IPFS.)

+ +

IPFS can end 404s through “pinning,” a way of mirroring a file across several different computers. It’s a way for computers to work together to serve content instead of working independently, thus gaining redundancy and performance benefits. I see a future where pinning files on IPFS becomes as easy as pinning a photo on Pinterest. Fans of ProjektMelody can pin the VODs on Futureporn, increasing that VOD’s replication and servability to future viewers.

+ +

But wait, there’s more! I have been thinking about a bunch of other stuff that could be done with past VODs. I think the most exciting thing would be to use computer vision to parse Melody’s vibrator activity from the video, and export to a data file. This data file could be used to send good vibes to a viewer’s vibrator in-sync with VOD playback. Feel what Melody feels! Very exciting, very sexy! This is a long-term goal for Futureporn.

+ +

I have several goals for Futureporn, as listed on the Goals page. A bunch of them have to do with increasing video playback performance, user interface design, but there’s a few that are pretty eccentric… Serving ProjektMelody VODs to Mars, for example!

+ +

I hope this site is useful to all the Science Team!

+ +
+
+

Futureporn needs financial support to continue improving. If you enjoy this website, please consider becoming a patron.

+
+
+ +
+
+
+ + ) +} \ No newline at end of file diff --git a/packages/next/app/blog/page.tsx b/packages/next/app/blog/page.tsx new file mode 100644 index 0000000..994e452 --- /dev/null +++ b/packages/next/app/blog/page.tsx @@ -0,0 +1,35 @@ +import Link from 'next/link'; +import { siteUrl } from '@/lib/constants'; +import { IBlogPost } from '@/lib/blog'; + + +export default async function PostsPage() { + const res = await fetch(`${siteUrl}/api/blogs`); + const posts: IBlogPost[] = [ + { + id: 1, + slug: '2021-10-29-the-story-of-futureporn', + title: 'The Story Of Futureporn' + } + ] + + return ( +
+
+ +

All Blog Posts

+
+ +
+ {posts.map((post: IBlogPost) => ( +
+ +

> {post.title}

+ +
+ ))} +
+
+
+ ); +} \ No newline at end of file diff --git a/packages/next/app/components/archive-progress.tsx b/packages/next/app/components/archive-progress.tsx new file mode 100644 index 0000000..2de480b --- /dev/null +++ b/packages/next/app/components/archive-progress.tsx @@ -0,0 +1,23 @@ +import { getAllStreamsForVtuber } from "@/lib/streams"; +import { IVtuber } from "@/lib/vtubers"; + +export interface IArchiveProgressProps { + vtuber: IVtuber; +} + +export default async function ArchiveProgress ({ vtuber }: IArchiveProgressProps) { + const streams = await getAllStreamsForVtuber(vtuber.id); + const goodStreams = await getAllStreamsForVtuber(vtuber.id, ['good']); + const issueStreams = await getAllStreamsForVtuber(vtuber.id, ['issue']); + const totalStreams = streams.length; + const eligibleStreams = issueStreams.length+goodStreams.length; + + // Check if totalStreams is not zero before calculating completedPercentage + const completedPercentage = (totalStreams !== 0) ? Math.round(eligibleStreams / totalStreams * 100) : 0; + return ( +
+

{eligibleStreams}/{totalStreams} Streams Archived ({completedPercentage}%)

+ {completedPercentage}% +
+ ) +} \ No newline at end of file diff --git a/packages/next/app/components/auth.tsx b/packages/next/app/components/auth.tsx new file mode 100644 index 0000000..e732be5 --- /dev/null +++ b/packages/next/app/components/auth.tsx @@ -0,0 +1,131 @@ +'use client'; + +import { createContext, useContext, ReactNode } from 'react'; +import { useRouter } from 'next/navigation'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faPatreon } from '@fortawesome/free-brands-svg-icons'; +import { useLocalStorageValue } from '@react-hookz/web'; +import { faRightFromBracket } from '@fortawesome/free-solid-svg-icons'; +import Skeleton from 'react-loading-skeleton'; +import { strapiUrl } from '@/lib/constants'; + +export interface IJWT { + jwt: string; + user: IUser; +} + +export interface IUser { + id: number; + username: string; + email: string; + provider: string; + confirmed: boolean; + blocked: boolean; + createdAt: string; + updatedAt: string; + isNamePublic: boolean; + avatar: string | null; + isLinkPublic: boolean; + vanityLink: string | null; + patreonBenefits: string; +} + +export interface IAuthData { + accessToken: string | null; + user: IUser | null; +} + +export interface IUseAuth { + authData: IAuthData | null | undefined; + setAuthData: (data: IAuthData | null) => void; + lastVisitedPath: string | undefined; + login: () => void; + logout: () => void; +} + +export const AuthContext = createContext(null); + +interface IAuthContextProps { + children: ReactNode; +} +export function AuthProvider({ children }: IAuthContextProps): React.JSX.Element { + const { value: authData, set: setAuthData } = useLocalStorageValue('authData', { + defaultValue: null, + }); + + const { value: lastVisitedPath, set: setLastVisitedPath } = useLocalStorageValue('lastVisitedPath', { + defaultValue: '/profile', + initializeWithValue: false, + }); + const router = useRouter(); + + const login = async () => { + const currentPath = window.location.pathname; + setLastVisitedPath(currentPath); + router.push(`${strapiUrl}/api/connect/patreon`); + }; + + const logout = () => { + setAuthData({ accessToken: null, user: null }); + }; + + return ( + + {children} + + ); +} + +export function LoginButton() { + const context = useContext(AuthContext); + if (!context) return ; + const { login } = context; + return ( + + ); +} + +export function LogoutButton() { + const context = useContext(AuthContext); + if (!context) return <>; + const { logout } = context; + return ( + + ); +} + +export function useAuth(): IUseAuth { + const context = useContext(AuthContext); + if (!context) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +} diff --git a/packages/next/app/components/cal.tsx b/packages/next/app/components/cal.tsx new file mode 100644 index 0000000..81ee719 --- /dev/null +++ b/packages/next/app/components/cal.tsx @@ -0,0 +1,125 @@ +'use client'; +// greets https://github.com/wa0x6e/cal-heatmap-react-starter/blob/main/src/components/cal-heatmap.tsx + +import CalHeatmap from 'cal-heatmap'; +// @ts-ignore cal-heatmap is jenk +import Legend from 'cal-heatmap/plugins/Legend'; +// @ts-ignore cal-heatmap is jenk +import Tooltip from 'cal-heatmap/plugins/Tooltip'; +import { DataRecord } from 'cal-heatmap/src/options/Options'; +import 'cal-heatmap/cal-heatmap.css'; +import dayjs from 'dayjs'; +import { useEffect, useState, useRef } from 'react'; +import { useRouter } from 'next/navigation'; +import { getSafeDate } from '@/lib/dates'; + +export interface ICalProps { + data: DataRecord[]; + slug: string; +} + + +export function Cal({ data, slug }: ICalProps) { + const router = useRouter(); + const [cellSize, setCellSize] = useState(13); + const [targetElementId, setTargetElementId] = useState(''); + + const generateUniqueId = () => { + return `cal-${Math.random().toString(36).substring(2, 9)}`; + }; + + + + useEffect(() => { + const updateCellSize = () => { + const windowWidth = window.innerWidth; + if (windowWidth > 1400) { + setCellSize(15); // Adjust the cell size for width > 1400px + } else if (windowWidth > 730) { + setCellSize(10); // Adjust the cell size for width > 730px + } else { + setCellSize(3); // Adjust the cell size for width <= 730px + } + } + updateCellSize(); + // Event listener to update cell size on window resize + window.addEventListener('resize', updateCellSize); + + return () => { + window.removeEventListener('resize', updateCellSize); + }; + + }, []) + + + useEffect(() => { + setTargetElementId(generateUniqueId()); + }, []); + + useEffect(() => { + if (!targetElementId) return; + const cal = new CalHeatmap(); + // @ts-ignore + cal.on('click', ( + event: string, + timestamp: number, + value: number + ) => { + router.push(`/vt/${slug}/stream/${getSafeDate(new Date(timestamp))}`); + // console.log(`slug=${slug} safeDate=${getSafeDate(new Date(timestamp))}`); + }); + + cal.paint( + { + itemSelector: `#${targetElementId}`, + scale: { + color: { + // @ts-ignore this shit is straight from the example website + domain: ['missing', 'issue', 'good'], + type: 'ordinal', + range: ['red', 'yellow', 'green'] + } + }, + theme: 'dark', + verticalOrientation: false, + data: { + source: data, + x: 'date', + y: 'value', + // @ts-ignore this shit is straight from the example website + groupY: d => d[0] + }, + range: 12, + date: { start: data[0].date }, + domain: { + type: 'month', + gutter: 4, + label: { text: 'MMM', textAlign: 'start', position: 'top' } + }, + subDomain: { + type: 'ghDay', + radius: 2, + width: cellSize, + height: cellSize, + gutter: 4, + } + }, [ + [ + Tooltip, + { + text: ((ts: number, value: string, dayjsDate: dayjs.Dayjs) => { + return `${!!value ? value+' - '+dayjsDate.toString() : dayjsDate.toString() }`; + }) + } + ] + ]); + + }, [targetElementId, data, cellSize, router, slug]); + + + return ( + <> +
+ + ) +} \ No newline at end of file diff --git a/packages/next/app/components/contributors.tsx b/packages/next/app/components/contributors.tsx new file mode 100644 index 0000000..74f877c --- /dev/null +++ b/packages/next/app/components/contributors.tsx @@ -0,0 +1,33 @@ +import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; +import { getContributors } from "../lib/contributors"; +import Link from 'next/link'; +import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +export default async function Contributors() { + const contributors = await getContributors(); + if (!contributors || contributors.length < 1) return ( + + + + ) + const contributorList = contributors.map((contributor, index) => ( + + {contributor.attributes.url ? ( + + {contributor.attributes.name} + + + ) : ( + contributor.attributes.name + )} + {index !== contributors.length - 1 ? ", " : ""} + + )); + return ( + <>{contributorList} + ) +} \ No newline at end of file diff --git a/packages/next/app/components/custom-hls-player.tsx b/packages/next/app/components/custom-hls-player.tsx new file mode 100644 index 0000000..f46724c --- /dev/null +++ b/packages/next/app/components/custom-hls-player.tsx @@ -0,0 +1,80 @@ +import React, { useEffect, useRef, useState, forwardRef, MutableRefObject } from "react"; +import { APITypes, PlyrProps, usePlyr } from "plyr-react"; +import "plyr-react/plyr.css"; +import { Options } from "plyr"; +import Hls from "hls.js"; + + +export function UnsupportedHlsMessage(): React.JSX.Element { + return ( +
+ HLS is not supported in your browser. Please try a different browser. +
+ ); +} + +const useHls = (src: string, options: Options | null) => { + const hls = useRef(new Hls()); + const hasQuality = useRef(false); + const [plyrOptions, setPlyrOptions] = useState(options); + + + + useEffect(() => { + hasQuality.current = false; + }, [options]); + + useEffect(() => { + hls.current.loadSource(src); + hls.current.attachMedia(document.querySelector(".plyr-react")!); + + hls.current.on(Hls.Events.MANIFEST_PARSED, () => { + if (hasQuality.current) return; // early quit if already set + + const levels = hls.current.levels; + const quality: Options["quality"] = { + default: levels[levels.length - 1].height, + options: levels.map((level) => level.height), + forced: true, + onChange: (newQuality: number) => { + levels.forEach((level, levelIndex) => { + if (level.height === newQuality) { + hls.current.currentLevel = levelIndex; + } + }); + }, + }; + + setPlyrOptions({ ...plyrOptions, quality }); + hasQuality.current = true; + }); + }); + + return { options: plyrOptions }; +}; + +const CustomPlyrInstance = forwardRef< + APITypes, + PlyrProps & { hlsSource: string; mainColor: string; plyrOptions: Options } +>((props, ref) => { + const { source, plyrOptions, hlsSource, mainColor } = props; + const plyrRef = usePlyr(ref, { + ...useHls(hlsSource, plyrOptions), + source, + }) as MutableRefObject; + + return ( + <> + + + ); +}); + + +CustomPlyrInstance.displayName = 'CustomPlyrInstance' + +export { CustomPlyrInstance } \ No newline at end of file diff --git a/packages/next/app/components/footer.tsx b/packages/next/app/components/footer.tsx new file mode 100644 index 0000000..ff4431e --- /dev/null +++ b/packages/next/app/components/footer.tsx @@ -0,0 +1,118 @@ +import Link from "next/link"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons"; +import { faGit, faReddit, faDiscord, faPatreon } from "@fortawesome/free-brands-svg-icons"; +import Contributors from "./contributors"; +import PatronsList from "./patrons-list"; + +export default function Footer() { + return ( + <> +
+
+ +
+
+

Sitemap

+
    +
  • ↑ Top of page
  • +
  • Vtubers
  • +
  • Stream Archive
  • +
  • About
  • +
  • FAQ
  • +
  • Goals
  • +
  • Patrons
  • +
  • Tags
  • +
  • RSS Feed
  • +
  • API
  • +
  • Blog
  • +
  • Status
  • +
  • Upload
  • +
  • Profile
  • +
+
+
+

+ Futureporn.net is made with ❤️ by CJ_Clippy +

+

+ Made possible by generous + + donations + + + + from + +

+

+ VOD contributions by +

+

+ + + Git Repo + + +

+ +

+ + + Reddit Thread + + +

+ +

+ + + Discord Server + + +

+
+
+ +
+ +
+ + ) +} \ No newline at end of file diff --git a/packages/next/app/components/funding-goal.tsx b/packages/next/app/components/funding-goal.tsx new file mode 100644 index 0000000..12b4119 --- /dev/null +++ b/packages/next/app/components/funding-goal.tsx @@ -0,0 +1,81 @@ + +import { getCampaign } from "@/lib/patreon"; +import { getGoals, IGoals } from '@/lib/pm' +import Image from 'next/image'; +import React from 'react'; +import Link from 'next/link' + + + +export default async function FundingGoal(): Promise { + const campaignData = await getCampaign(); + const { pledgeSum, patronCount } = campaignData; + + const goals = await getGoals(pledgeSum); + if (!goals || !goals?.featuredFunded?.amountCents || !goals?.featuredUnfunded?.amountCents || !goals?.featuredFunded?.amountCents || !goals?.featuredUnfunded?.completedPercentage || !goals?.featuredFunded?.completedPercentage ) return <> + + return ( + <> + {/*

+ pledgeSum:{JSON.stringify(pledgeSum, null, 2)} +

+

+ patronCount:{JSON.stringify(patronCount, null, 2)} +

+

featuredFunded:{JSON.stringify(goals.featuredFunded)}

+

featuredUnfunded:{JSON.stringify(goals.featuredUnfunded)}

*/} + + {/*
+                
+                    {JSON.stringify(goals, null, 2)}
+                
+            
*/} + +
+
+ Funding Goal +
+ + CJ_Clippy + +
+
+
+
+ {/* the most recently funded goal */} +
+ {/* const { featuredFunded, featuredUnfunded } = goals; + if (!featuredFunded?.amountCents || !featuredFunded?.completedPercentage) return <> + if (!featuredUnfunded?.amountCents || !featuredUnfunded?.completedPercentage) return <> */} + +

${(goals.featuredFunded.amountCents * (goals.featuredFunded.completedPercentage * 0.01) / 100)} of {goals.featuredFunded.amountCents / 100} ({goals.featuredFunded.completedPercentage}%) +

+
+ FUNDED +
+

{goals.featuredFunded.description}

+
+ + {/* the next unfunded goal */} +
+

${(goals.featuredUnfunded.amountCents * (goals.featuredUnfunded.completedPercentage * 0.01) / 100) | 0} of ${goals.featuredUnfunded.amountCents / 100} ({goals.featuredUnfunded.completedPercentage}%)

+ + {goals.featuredUnfunded.completedPercentage}% + +

{goals.featuredUnfunded.description}

+
+
+ +

+ Thank you, Patrons! +

+
+
+ + ); +}; + diff --git a/packages/next/app/components/icons/carrd.tsx b/packages/next/app/components/icons/carrd.tsx new file mode 100644 index 0000000..d900cda --- /dev/null +++ b/packages/next/app/components/icons/carrd.tsx @@ -0,0 +1,8 @@ +import * as React from "react" +const SvgComponent = (props: any) => ( + + {"Carrd"} + + +) +export default SvgComponent diff --git a/packages/next/app/components/icons/chaturbate.tsx b/packages/next/app/components/icons/chaturbate.tsx new file mode 100644 index 0000000..31c641f --- /dev/null +++ b/packages/next/app/components/icons/chaturbate.tsx @@ -0,0 +1,14 @@ +import * as React from "react" +const SvgComponent = (props: any) => ( + + + +) +export default SvgComponent diff --git a/packages/next/app/components/icons/fansly.tsx b/packages/next/app/components/icons/fansly.tsx new file mode 100644 index 0000000..03a78dc --- /dev/null +++ b/packages/next/app/components/icons/fansly.tsx @@ -0,0 +1,20 @@ +import * as React from "react" +const SvgComponent = (props: any) => ( + + + + +) +export default SvgComponent diff --git a/packages/next/app/components/icons/linktree.tsx b/packages/next/app/components/icons/linktree.tsx new file mode 100644 index 0000000..3e17f8b --- /dev/null +++ b/packages/next/app/components/icons/linktree.tsx @@ -0,0 +1,30 @@ +import * as React from "react" +const SvgComponent = (props: any) => ( + + + + + + + + + + + +) +export default SvgComponent diff --git a/packages/next/app/components/icons/onlyfans.tsx b/packages/next/app/components/icons/onlyfans.tsx new file mode 100644 index 0000000..81a568a --- /dev/null +++ b/packages/next/app/components/icons/onlyfans.tsx @@ -0,0 +1,55 @@ +import * as React from "react" +const SvgComponent = (props: any) => ( + + + + + + +) +export default SvgComponent diff --git a/packages/next/app/components/icons/pornhub.tsx b/packages/next/app/components/icons/pornhub.tsx new file mode 100644 index 0000000..5f7a746 --- /dev/null +++ b/packages/next/app/components/icons/pornhub.tsx @@ -0,0 +1,23 @@ +import * as React from "react" +const SvgComponent = (props: any) => ( + + + + + + + +) +export default SvgComponent diff --git a/packages/next/app/components/icons/throne.tsx b/packages/next/app/components/icons/throne.tsx new file mode 100644 index 0000000..897285c --- /dev/null +++ b/packages/next/app/components/icons/throne.tsx @@ -0,0 +1,31 @@ +import * as React from "react" +const SvgComponent = (props: any) => ( + + + + + + + + + + + + +) +export default SvgComponent diff --git a/packages/next/app/components/ipfs-cid.tsx b/packages/next/app/components/ipfs-cid.tsx new file mode 100644 index 0000000..efbde66 --- /dev/null +++ b/packages/next/app/components/ipfs-cid.tsx @@ -0,0 +1,42 @@ +'use client'; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCopy, faCheck } from "@fortawesome/free-solid-svg-icons"; +import { useState } from "react"; +import styles from '@/assets/styles/cid.module.css' + +interface IIpfsCidProps { + label?: string; + cid: string; +} + + +export function IpfsCid({ label, cid }: IIpfsCidProps) { + + const [isCopied, setIsCopied] = useState(false); + + + + return ( +
+ {label} +
{cid}
+ {(isCopied) ? + + : + { + navigator.clipboard.writeText(cid) + setIsCopied(true) + setTimeout(() => setIsCopied(false), 3000) + }} + > + } +
+ ) +} diff --git a/packages/next/app/components/ipfs-logo.tsx b/packages/next/app/components/ipfs-logo.tsx new file mode 100644 index 0000000..3875418 --- /dev/null +++ b/packages/next/app/components/ipfs-logo.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +interface LogoProps { + size: number; + color: string; +} + +const IPFSLogo: React.FC = ({ size = 32, color = '#65C2CB' }) => { + + return ( + + IPFS + + + ); +}; + +export default IPFSLogo; \ No newline at end of file diff --git a/packages/next/app/components/ipfs.tsx b/packages/next/app/components/ipfs.tsx new file mode 100644 index 0000000..cacfea8 --- /dev/null +++ b/packages/next/app/components/ipfs.tsx @@ -0,0 +1,39 @@ +'use client'; + +// import { type Helia, createHelia } from 'helia'; +// import React, { useState, useEffect } from 'react'; + +// export default function Ipfs () { +// const [id, setId] = useState(null) +// const [helia, setHelia] = useState(null) +// const [isOnline, setIsOnline] = useState(false) + +// useEffect(() => { +// const init = async () => { +// if (helia) return + +// const heliaNode = await createHelia(); + +// const nodeId = heliaNode.libp2p.peerId.toString(); +// const nodeIsOnline = heliaNode.libp2p.isStarted(); + +// setHelia(heliaNode); +// setId(nodeId); +// setIsOnline(nodeIsOnline); +// } + +// init() +// }, [helia]) + +// if (!helia || !id) { +// return

Connecting to IPFS...

+// } + +// return ( +//
+//

ID: {id.toString()}

+//

Status: {isOnline ? 'Online' : 'Offline'}

+//
+// ) +// } + diff --git a/packages/next/app/components/linkable-heading.tsx b/packages/next/app/components/linkable-heading.tsx new file mode 100644 index 0000000..c5e782a --- /dev/null +++ b/packages/next/app/components/linkable-heading.tsx @@ -0,0 +1,28 @@ +import Link from "next/link"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { IconDefinition, faLink } from "@fortawesome/free-solid-svg-icons"; + +interface ILinkableHeadingProps { + icon?: IconDefinition; + text: string; + slug: string; +} + +export default function LinkableHeading({ icon, text, slug }: ILinkableHeadingProps) { + return ( +

+ {icon && } + {text} + + + + +

+ ) +} \ No newline at end of file diff --git a/packages/next/app/components/localized-date.tsx b/packages/next/app/components/localized-date.tsx new file mode 100644 index 0000000..65ec1c5 --- /dev/null +++ b/packages/next/app/components/localized-date.tsx @@ -0,0 +1,15 @@ +import { formatISO } from "date-fns"; + +interface ILocalizedDateProps { + date: Date; +} + +export function LocalizedDate ({ date }: ILocalizedDateProps) { + const isoDateTime = formatISO(date); + const isoDate = formatISO(date, { representation: 'date' }); + return ( + <> + + + ) +} \ No newline at end of file diff --git a/packages/next/app/components/navbar.tsx b/packages/next/app/components/navbar.tsx new file mode 100644 index 0000000..57fca00 --- /dev/null +++ b/packages/next/app/components/navbar.tsx @@ -0,0 +1,98 @@ +'use client' + +import { useEffect, useState } from 'react' +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons"; +import { faUser, faUpload } from "@fortawesome/free-solid-svg-icons"; +import Link from 'next/link' +import { LoginButton, useAuth } from '@/components/auth' + + +export default function Navbar() { + const [isExpanded, setExpanded] = useState(false); + const [isProfileButton, setIsProfileButton] = useState(false); + + const handleBurgerClick = () => { + setExpanded(!isExpanded); + }; + + const { authData } = useAuth() + + useEffect(() => { + if (!!authData?.accessToken && !!authData?.user?.username) setIsProfileButton(true) + else setIsProfileButton(false) + }, [authData]) + + return ( + <> + + + ) +} \ No newline at end of file diff --git a/packages/next/app/components/notification-center.tsx b/packages/next/app/components/notification-center.tsx new file mode 100644 index 0000000..31218c6 --- /dev/null +++ b/packages/next/app/components/notification-center.tsx @@ -0,0 +1,13 @@ +'use client'; + +import { ToastContainer } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; + + +export default function NotificationCenter() { + return ( + + ) +} \ No newline at end of file diff --git a/packages/next/app/components/notifications.tsx b/packages/next/app/components/notifications.tsx new file mode 100644 index 0000000..1968ecd --- /dev/null +++ b/packages/next/app/components/notifications.tsx @@ -0,0 +1,10 @@ + +export function DangerNotification ({ errors }: { errors: String[] }): JSX.Element { + return ( +
+ {errors && errors.map((error, index) => ( +

Error:{error}

+ ))} +
+ ); +} \ No newline at end of file diff --git a/packages/next/app/components/pager.tsx b/packages/next/app/components/pager.tsx new file mode 100644 index 0000000..01f67ab --- /dev/null +++ b/packages/next/app/components/pager.tsx @@ -0,0 +1,82 @@ +import Link from 'next/link'; + +interface IPagerProps { + baseUrl: string; // Pass the base URL as a prop + page: number; + pageCount: number; +} + +export default function Pager({ baseUrl, page, pageCount }: IPagerProps): React.JSX.Element { + const pageNumbers = Array.from({ length: pageCount }, (_, i) => i + 1); + + const getPagePath = (page: any) => { + const pageNumber = parseInt(page); + return `${baseUrl}/${pageNumber}`; + }; + + // Define the number of page links to show around the current page + const maxPageLinksToShow = 3; + + // Calculate the range of page numbers to display + const startPage = Math.max(1, page - Math.floor(maxPageLinksToShow / 2)); + const endPage = Math.min(pageCount, startPage + maxPageLinksToShow - 1); + + return ( +
+ +
+ ); +} diff --git a/packages/next/app/components/patrons-list.tsx b/packages/next/app/components/patrons-list.tsx new file mode 100644 index 0000000..f54961e --- /dev/null +++ b/packages/next/app/components/patrons-list.tsx @@ -0,0 +1,55 @@ +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import 'react-loading-skeleton/dist/skeleton.css'; +import { getPatrons } from '../lib/patreon'; +import Link from 'next/link' + +interface PatronsListProps { + displayStyle: string; +} + +export default async function PatronsList({ displayStyle }: PatronsListProps) { + const patrons = await getPatrons() + + if (!patrons) return ( + + + + ); + if (displayStyle === 'box') { + return ( +
+ {patrons.map((patron) => ( +
+
+
+
+
+ {patron.username && ( + + {patron.username} + + )} + {patron.vanityLink && ( + + {patron.vanityLink} + + + + + )} +
+
+
+
+
+ ))} +
+ ); + } else if (displayStyle === 'list') { + const patronNames = patrons.map((patron) => patron.username.trim()).join(', '); + return {patronNames}; + } else { + return ; // Handle unsupported display styles or provide a default display style + } +} + diff --git a/packages/next/app/components/sortable-tags.tsx b/packages/next/app/components/sortable-tags.tsx new file mode 100644 index 0000000..669b02d --- /dev/null +++ b/packages/next/app/components/sortable-tags.tsx @@ -0,0 +1,70 @@ +'use client' + +import React, { useState } from 'react'; +import { ITag } from '../lib/tags'; +import Link from 'next/link'; +import slugify from 'slugify'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faFilter } from "@fortawesome/free-solid-svg-icons"; + +interface ISortableTagsProps { + tags: ITag[]; +} + +export default function SortableTags({ tags }: ISortableTagsProps) { + const [filterText, setFilterText] = useState(''); + const [sortOption, setSortOption] = useState('Sort'); + + const filteredTags = tags.filter((tag: ITag) => + tag.attributes.name.toLowerCase().includes(filterText.toLowerCase()) + ); + + const sortedTags = [...filteredTags].sort((a, b) => { + if (sortOption === 'Alphabetical') { + return a.attributes.name.localeCompare(b.attributes.name); + } else if (sortOption === 'Frequency') { + return b.attributes.count - a.attributes.count; + } + return 0; + }); + + return ( + <> +
+
+ setFilterText(e.target.value)} + /> + + + +
+
+
+ +
+
+
+
+ {sortedTags.map((tag: ITag) => ( + + + {tag.attributes.name} ({tag.attributes.count}) + + + ))} +
+ + ); +} diff --git a/packages/next/app/components/stream-button.tsx b/packages/next/app/components/stream-button.tsx new file mode 100644 index 0000000..5c20280 --- /dev/null +++ b/packages/next/app/components/stream-button.tsx @@ -0,0 +1,19 @@ +import { IStream } from "@/lib/streams"; +import Link from "next/link" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCalendar } from "@fortawesome/free-solid-svg-icons"; + +export function StreamButton({ stream }: { stream: IStream }) { + if (!stream) return <> + // return

{JSON.stringify(stream, null, 2)}

+ // return {new Date(stream.attributes.date).toLocaleDateString()} + + return ( + + {new Date(stream.attributes.date).toLocaleDateString()} + + ) +} \ No newline at end of file diff --git a/packages/next/app/components/stream-page.tsx b/packages/next/app/components/stream-page.tsx new file mode 100644 index 0000000..7264b2b --- /dev/null +++ b/packages/next/app/components/stream-page.tsx @@ -0,0 +1,187 @@ +'use client'; + +import { IStream } from "@/lib/streams"; +import NotFound from "app/streams/[cuid]/not-found"; +import { IVod } from "@/lib/vods"; +import Link from "next/link"; +import Image from "next/image"; +import { LocalizedDate } from "./localized-date"; +import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome"; +import { faTriangleExclamation, faCircleInfo, faThumbsUp, IconDefinition, faO, faX, faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons"; +import { Hemisphere, Moon } from "lunarphase-js"; +import { useEffect, useState } from "react"; +import { faXTwitter } from "@fortawesome/free-brands-svg-icons"; + +export interface IStreamProps { + stream: IStream; +} +type Status = 'missing' | 'issue' | 'good'; +interface StyleDef { + heading: string; + icon: IconDefinition; + desc1: string; + desc2: string; +} + +function capitalizeFirstLetter(string: string): string { + return string.charAt(0).toUpperCase() + string.slice(1); +} + +function hasNote(vod: IVod) { + if (!!vod?.attributes?.note) return true; + else return false; +} + +function determineStatus(stream: IStream): Status { + if (stream.attributes.vods.data.length < 1) { + return 'missing' + } else { + if (stream.attributes.vods.data.some(vod => !hasNote(vod))) { + return 'good'; + } else { + return 'issue'; + } + } +} + +export default function StreamPage({ stream }: IStreamProps) { + const displayName = stream.attributes.vtuber.data.attributes.displayName; + const date = new Date(stream.attributes.date); + const [hemisphere, setHemisphere] = useState(Hemisphere.NORTHERN); + const [selectedStatus, setSelectedStatus] = useState(determineStatus(stream)); + + const styleMap: Record = { + 'missing': { + heading: 'is-danger', + icon: faTriangleExclamation, + desc1: "We don't have a VOD for this stream.", + desc2: 'Know someone who does?' + }, + 'issue': { + heading: 'is-warning', + icon: faCircleInfo, + desc1: "We have a VOD for this stream, but it's not full quality.", + desc2: 'Have a better copy?' + }, + 'good': { + heading: 'is-success', + icon: faThumbsUp, + desc1: "We have a VOD for this stream, and we think it's the best quality possible.", + desc2: "Have one that's even better?" + } + }; + const { heading, icon, desc1, desc2 } = styleMap[selectedStatus] || {}; + + useEffect(() => { + const randomHemisphere = (Math.random() < 0.5 ? 0 : 1) ? Hemisphere.NORTHERN : Hemisphere.SOUTHERN; + setHemisphere(randomHemisphere); + }, []); + + if (!stream) return + + // return

+ //

+    //         
+    //             {JSON.stringify(stream, null, 2)}
+
+    //         
+    //     
+ + //

+ // const platformsList = '???'; + const { isChaturbateInvite, isFanslyInvite } = stream.attributes.tweet.data.attributes; + const platformsArray = [ + isChaturbateInvite ? 'Chaturbate' : null, + isFanslyInvite ? 'Fansly' : null + ].filter(Boolean); + const platformsList = platformsArray.length > 0 ? platformsArray.join(', ') : 'None'; + + + return ( + <> + + +
+
+

{displayName} Stream Archive

+
+ +
+
+
+

Details

+
+
+ Announcement 

+ Platform {platformsList}

+ UTC Datetime 

+ Local Datetime {date.toLocaleDateString()} {date.toLocaleTimeString()}

+ Lunar Phase {Moon.lunarPhase(date)} {Moon.lunarPhaseEmoji(date, { hemisphere })}

+

+ {/* */} +
+
+
+
+ + +
+
+
+ VOD {capitalizeFirstLetter(selectedStatus)} +
+
+ +

{desc1}

+

{desc2}
+ Upload it here.

+
+
+
+ +
+ + +
+

VODs

+ + + + + + {/* + */} + + + + + + + {stream.attributes.vods.data.map((vod: IVod) => ( + + {/*

{JSON.stringify(vod, null, 2)}

*/} + + + {/* + */} + + + + + ))} + +
IDUpload DateThumbnailDurationTagsTimestampsNote
{vod.attributes.cuid}{vod.attributes.publishedAt}{(!!vod?.attributes?.thumbnail?.data?.attributes?.cdnUrl) ? : }{(!!vod?.attributes?.duration) ? vod.attributes.duration : }{vod.attributes.tagVodRelations.data.length}{vod.attributes.timestamps.data.length}{(!!vod.attributes.note) ? : }
+
+ +
+ + + ) +} diff --git a/packages/next/app/components/stream.tsx b/packages/next/app/components/stream.tsx new file mode 100644 index 0000000..3e54a19 --- /dev/null +++ b/packages/next/app/components/stream.tsx @@ -0,0 +1,86 @@ +import { IStream } from "@/lib/streams"; +import NotFound from "app/vt/[slug]/not-found"; +import { LocalizedDate } from "./localized-date"; +import Link from "next/link"; +import ChaturbateIcon from "@/components/icons/chaturbate"; +import FanslyIcon from "@/components/icons/fansly"; +import Image from "next/image"; + +export interface IStreamProps { + stream: IStream; +} + + +export function Stream({ stream }: IStreamProps) { + if (!stream) return + return ( +
+
+                
+                    {JSON.stringify(stream, null, 2)}
+                
+            
+ {/*

Stream {stream.attributes.date}

*/} +
+ ) +} + + + +export function StreamSummary ({ stream }: IStreamProps) { + if (!stream) return + + // return ( + //
+    //         
+    //             {JSON.stringify(stream, null, 2)}
+    //         
+    //     
+ // ) + + const archiveStatus = stream.attributes.archiveStatus; + const archiveStatusClassName = (() => { + if (archiveStatus === 'missing') return 'is-danger'; + if (archiveStatus === 'good') return 'is-success'; + if (archiveStatus === 'issue') return 'is-warning'; + })(); + + return ( + +
+ {/*
+                    
+                        {JSON.stringify(stream, null, 2)}
+                    
+                
*/} +
+ + {stream.attributes.vtuber.data.attributes.displayName} + +
+
+ {stream.attributes.vtuber.data.attributes.displayName} +
+
+ +
+
+ {(stream.attributes.isChaturbateStream) && } + {(stream.attributes.isFanslyStream) && } +
+
+
{stream.attributes.archiveStatus}
+
+
+
+
+
+ + ) +} \ No newline at end of file diff --git a/packages/next/app/components/streams-calendar.tsx b/packages/next/app/components/streams-calendar.tsx new file mode 100644 index 0000000..fb04263 --- /dev/null +++ b/packages/next/app/components/streams-calendar.tsx @@ -0,0 +1,83 @@ +'use client'; + +import FullCalendar from "@fullcalendar/react"; +import interactionPlugin, { DateClickArg } from '@fullcalendar/interaction'; +import dayGridPlugin from '@fullcalendar/daygrid'; +import multiMonthPlugin from '@fullcalendar/multimonth' + +import { IStream } from "@/lib/streams"; +import { useRouter } from 'next/navigation'; +import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime"; + + + +interface IStreamsCalendarProps { + missingStreams: IStream[]; + issueStreams: IStream[]; + goodStreams: IStream[]; +} + +interface IEvent { + cuid: string; + start: Date; + end?: Date; + title: string; + vtuber: string; +} + +// function buildStreamPageUrlFromDate(date: Date) { +// // const cuid = +// return `/s/${safeDate}`; +// } + +function handleEventClick(info: any, router: AppRouterInstance) { + var eventObj = info.event; + const { cuid } = eventObj._def.extendedProps; + router.push(`/streams/${cuid}`); + +} + +function convertStreamToEvent(stream: IStream): IEvent { + console.log(stream) + const displayName = stream.attributes.vtuber.data.attributes.displayName; + return { + cuid: stream.attributes.cuid, + start: new Date(stream.attributes.date), + title: `${displayName}`, + vtuber: displayName + } +} + +export default function StreamsCalendar({ missingStreams, issueStreams, goodStreams }: IStreamsCalendarProps) { + const router = useRouter(); + const eventSources = [ + { + events: missingStreams.map(convertStreamToEvent), + color: 'red' + }, + { + events: issueStreams.map(convertStreamToEvent), + color: 'yellow', + }, + { + events: goodStreams.map(convertStreamToEvent), + color: 'green' + } + ] + + return ( + <> + { + handleEventClick(args, router); + }} + /> + + ) +} \ No newline at end of file diff --git a/packages/next/app/components/streams-list.tsx b/packages/next/app/components/streams-list.tsx new file mode 100644 index 0000000..b70110a --- /dev/null +++ b/packages/next/app/components/streams-list.tsx @@ -0,0 +1,61 @@ +import React from 'react' +import Link from 'next/link'; +import VodCard from './vod-card'; +import { IVtuber } from '@/lib/vtubers'; +import { IVod } from '@/lib/vods'; +import { getVodTitle } from './vod-page'; +import { notFound } from 'next/navigation'; +import { IStream, getStreamsForVtuber, getAllStreams } from '@/lib/streams'; +import { StreamSummary } from '@/components/stream'; + +interface IStreamsListProps { + vtubers: IVtuber[]; + page: number; + pageSize: number; +} + + +interface IStreamsListHeadingProps { + slug: string; + displayName: string; +} + +export function StreamsListHeading({ slug, displayName }: IStreamsListHeadingProps): React.JSX.Element { + return ( +
+

+ {displayName} Streams +

+
+ ) +} + + +export default async function StreamsList({ vtubers, page = 1, pageSize = 24 }: IStreamsListProps): Promise { + if (!vtubers) return
vtubers is not defined. vtubers:{JSON.stringify(vtubers, null, 2)}
+ + // const streams = await getStreamsForVtuber(vtubers[0].id); + const streams = await getAllStreams(['missing', 'issue', 'good']); + + if (!streams) return notFound(); + + + // @todo [ ] pagination + // @todo [ ] sortability + return ( + <> + +

Stream Archive

+ + + ); +} diff --git a/packages/next/app/components/tag-button.tsx b/packages/next/app/components/tag-button.tsx new file mode 100644 index 0000000..dda1ea5 --- /dev/null +++ b/packages/next/app/components/tag-button.tsx @@ -0,0 +1,8 @@ + +import { useState } from 'react'; + +export function TagButton ({ name, selectedTag, setSelectedTag }: { name: string, selectedTag: string | null, setSelectedTag: Function }) { + return ( + + ) +} \ No newline at end of file diff --git a/packages/next/app/components/tag.tsx b/packages/next/app/components/tag.tsx new file mode 100644 index 0000000..cc043dd --- /dev/null +++ b/packages/next/app/components/tag.tsx @@ -0,0 +1,57 @@ +'use client'; + +import { ITagVodRelation, ITagVodRelationsResponse } from "@/lib/tag-vod-relations" +import { isWithinInterval, subHours } from "date-fns"; +import { faTrash } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { AuthContext, IUseAuth } from "./auth"; +import { useContext, useEffect, useState } from "react"; +import { useRouter } from 'next/navigation'; +import { strapiUrl } from "@/lib/constants"; + +export interface ITagParams { + tvr: ITagVodRelation; +} + + +function isCreatedByMeRecently(userId: number | undefined, tvr: ITagVodRelation) { + if (!userId) return false; + if (userId !== tvr.attributes.creatorId) return false; + const last24H: Interval = { start: subHours(new Date(), 24), end: new Date() }; + if (!isWithinInterval(new Date(tvr.attributes.createdAt), last24H)) return false; + return true; +} + +async function handleDelete(authContext: IUseAuth | null, tvr: ITagVodRelation): Promise { + if (!authContext) return; + const { authData } = authContext; + const res = await fetch(`${strapiUrl}/api/tag-vod-relations/deleteMine/${tvr.id}`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${authData?.accessToken}`, + 'Content-Type': 'application/json' + } + }) + if (!res.ok) throw new Error(res.statusText) +} + +export function Tag({ tvr }: ITagParams) { + const authContext = useContext(AuthContext); + const router = useRouter() + const [shouldRenderDeleteButton, setShouldRenderDeleteButton] = useState(false); + + useEffect(() => { + setShouldRenderDeleteButton(isCreatedByMeRecently(authContext?.authData?.user?.id, tvr)); + }, [authContext?.authData?.user?.id, tvr]); + + return ( + + {tvr.attributes.tag.data.attributes.name} + {shouldRenderDeleteButton && { + handleDelete(authContext, tvr); router.refresh() + } + } className="tag">} + + ) +} \ No newline at end of file diff --git a/packages/next/app/components/tagger.tsx b/packages/next/app/components/tagger.tsx new file mode 100644 index 0000000..2918642 --- /dev/null +++ b/packages/next/app/components/tagger.tsx @@ -0,0 +1,240 @@ +'use client'; + +import { useState, useCallback, useEffect, useContext } from 'react'; +import { IVod } from '@/lib/vods'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faPlus, faX, faTags } from "@fortawesome/free-solid-svg-icons"; +import { formatTimestamp } from '@/lib/dates'; +import { readOrCreateTagVodRelation } from '@/lib/tag-vod-relations'; +import { readOrCreateTag } from '@/lib/tags'; +import { useAuth } from './auth'; +import { debounce } from 'lodash'; +import { strapiUrl } from '@/lib/constants'; +import { VideoContext } from './video-context'; +import { useForm } from "react-hook-form"; +import { ITimestamp, createTimestamp } from '@/lib/timestamps'; +import { useRouter } from 'next/navigation'; +import styles from '@/assets/styles/fp.module.css' +import qs from 'qs'; +import { toast } from 'react-toastify'; +import slugify from 'slugify'; + +interface ITaggerProps { + vod: IVod; + setTimestamps: Function; +} + +export interface ITagSuggestion { + id: number; + name: string; + createdAt: string; +} + + +type FormData = { + tagName: string; + isTimestamp: boolean; +}; + + + + + +export function Tagger({ vod, setTimestamps }: ITaggerProps): React.JSX.Element { + + const { register, setValue, setError, setFocus, handleSubmit, watch, clearErrors, formState: { errors } } = useForm({ + defaultValues: { + tagName: '', + isTimestamp: true + } + }); + const [isEditor, setIsEditor] = useState(false); + const [isAuthed, setIsAuthed] = useState(false); + const [tagSuggestions, setTagSuggestions] = useState([]); + const { authData } = useAuth(); + const { timeStamp, tvrs, setTvrs } = useContext(VideoContext); + const router = useRouter(); + + const request = debounce((value: string) => { + search(value); + }, 300); + + const debounceRequest = useCallback((v: string) => request(v), [request]); + + + // Callback version of watch. It's your responsibility to unsubscribe when done. + useEffect(() => { + const subscription = watch((value, { name, type }) => { + const tagNameValue = value.tagName as string; + if (name === 'tagName' && type === 'change' && value.tagName !== '') debounceRequest(tagNameValue); + }); + return () => subscription.unsubscribe(); + }, [watch, debounceRequest]); + + + useEffect(() => { + if (isEditor) { + setFocus('tagName'); + getRandomSuggestions(); + } + }, [isEditor, setFocus]); + + useEffect(() => { + if (authData?.accessToken) { + setIsAuthed(true); + } + }, [isAuthed]); + + + async function getRandomSuggestions() { + const res = await fetch(`${strapiUrl}/api/tag/random`); + const tags = await res.json(); + setTagSuggestions(tags) + } + + async function search(value: string) { + const query = qs.stringify( + { + filters: { + tags: { + publishedAt: { + $notNull: true + } + } + }, + query: value + } + ) + if (!value) return; + const res = await fetch(`${strapiUrl}/api/fuzzy-search/search?${query}`, { + headers: { + 'Authorization': `Bearer ${authData?.accessToken}` + } + }) + const json = await res.json() + if (!res.ok) { + toast('failed to get recomended tags', { type: 'error', theme: 'dark' }); + } else { + setTagSuggestions(json.tags) + } + } + + + async function onError(errors: any) { + console.error('submit handler encoutnered an error'); + console.error(errors); + toast('there was an error'); + } + + async function onSubmit(values: { tagName: string, isTimestamp: boolean }) { + if (!authData?.accessToken) { + toast('must be logged in', { type: 'error', theme: 'dark' }); + return + } + try { + + const tag = await readOrCreateTag(authData.accessToken, slugify(values.tagName)); + if (!tag) throw new Error(`readOrCreateTag failed`); + + + const tvr = await readOrCreateTagVodRelation(authData.accessToken, tag.id, vod.id); + console.log(`now we check to see if we have a TVR`); + console.log(tvr) + + if (values.isTimestamp) { + console.log(`user specified that we must create a timestamp`); + const timestamp = await createTimestamp(authData, tag.id, vod.id, timeStamp); + console.log(timestamp) + if (!timestamp) throw new Error(`failed to create timestamp`) + setTimestamps((prevTimestamps: ITimestamp[]) => [...prevTimestamps, timestamp]); + } + + setValue('tagName', ''); + router.refresh(); + } catch (e) { + toast(`${e}`, { type: 'error', theme: 'dark' }); + } + } + + if (!isAuthed) { + return <> + } else { + if (isEditor) { + return ( +
+ + +
+

Tagger

+ +
+
+
+
+ + +
+
+ Suggestions + {tagSuggestions.length > 0 && tagSuggestions.map((tag: ITagSuggestion) => ())} +
+
+ +
+ +
+ {(!!errors?.root?.serverError) &&
{errors.root.serverError.message}
} + + +
+
+
+
+ ) + } else { + return ( + + ); + } + } + + +} diff --git a/packages/next/app/components/timestamps-list.tsx b/packages/next/app/components/timestamps-list.tsx new file mode 100644 index 0000000..e2ad0a0 --- /dev/null +++ b/packages/next/app/components/timestamps-list.tsx @@ -0,0 +1,72 @@ +import React, { useContext, useState, useEffect } from "react"; +import { IVod } from "@/lib/vods"; +import { + ITimestamp, + deleteTimestamp +} from "@/lib/timestamps"; +import { + formatTimestamp, + formatUrlTimestamp, +} from "@/lib/dates"; +import Link from 'next/link'; +import { faClock, faLink, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { AuthContext, IAuthData } from "./auth"; +import { isWithinInterval, subHours, Interval } from 'date-fns'; +import { useRouter } from 'next/navigation'; + +export interface ITimestampsProps { + vod: IVod; + timestamps: ITimestamp[]; + setTimestamps: Function; +} + +function isCreatedByMeRecently(authData: IAuthData, ts: ITimestamp) { + if (!authData?.user) return false; + if (authData.user.id !== ts.attributes.creatorId) return false; + const last24H: Interval = { start: subHours(new Date(), 24), end: new Date() }; + return isWithinInterval(new Date(ts.attributes.createdAt), last24H); +} + + +export function TimestampsList({ vod, timestamps, setTimestamps }: ITimestampsProps): React.JSX.Element { + // const throttledTimestampFetch = throttle(getRawTimestampsForVod); + const authContext = useContext(AuthContext); + + + const hasTimestamps = timestamps.length > 0; + + return ( +
+ + + {hasTimestamps && ( + timestamps.map((ts: ITimestamp) => ( +

+ {/* {JSON.stringify(ts, null, 2)}


*/} + + {formatTimestamp(ts.attributes.time)} + {' '} + {ts.attributes.tag.data.attributes.name} + {authContext?.authData && isCreatedByMeRecently(authContext.authData, ts) && ( + + )} +

+ )) + )} + + {!hasTimestamps &&

This VOD has no timestamps

} +
+ ); +} diff --git a/packages/next/app/components/toys.tsx b/packages/next/app/components/toys.tsx new file mode 100644 index 0000000..564d0ef --- /dev/null +++ b/packages/next/app/components/toys.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { IToy, IToysResponse } from '@/lib/toys'; +import { IVtuber } from '@/lib/vtubers'; +import Link from 'next/link'; +import Image from 'next/image'; + +export interface IToyProps { + toy: IToy; +} + +export interface IToysListsProps { + vtuber: IVtuber; + toys: IToysResponse; + page: number; + pageSize: number; +} + +// interface VodsListProps { +// vtuber: IVtuber; +// vods: IVods; +// page: number; +// pageSize: number; +// } + + + +export function ToysListHeading({ slug, displayName }: { slug: string, displayName: string }): React.JSX.Element { + return ( +
+

+ {displayName}'s Toys +

+
+ ) +} + +// export interface IToy { +// id: number; +// tags: ITag[]; +// linkTag: ITag; +// make: string; +// model: string; +// aspectRatio: string; +// image2: string; +// } + +export function ToyItem({ toy }: IToyProps) { + const displayName = `${toy.attributes.make} ${toy.attributes.model}`; + // if (!toy?.linkTag) return
toy.linkTag is missing which is a problem
+ return ( +
+ + +
+ {displayName} +
+

{toy.attributes.model}

+ +
+ ); +}; + +export function ToysList({ vtuber, toys, page = 1, pageSize = 24 }: IToysListsProps) { + return ( +
+ {/*
{JSON.stringify(toys, null, 2)} toys:{toys.data.length} page:{page} pageSize:{pageSize}
*/} +
+ {toys.data.map((toy: IToy) => ( + //

{JSON.stringify(toy, null, 2)}

+ + ))} +
+
+ ) +}; diff --git a/packages/next/app/components/upload-form.tsx b/packages/next/app/components/upload-form.tsx new file mode 100644 index 0000000..9283822 --- /dev/null +++ b/packages/next/app/components/upload-form.tsx @@ -0,0 +1,327 @@ +'use client'; + +import { IVtuber } from "@/lib/vtubers"; +import { useSearchParams } from 'next/navigation'; +import React, { useContext, useState, useEffect } from 'react'; +import { UppyContext } from 'app/uppy'; +import { LoginButton, useAuth } from '@/components/auth'; +import { Dashboard } from '@uppy/react'; +import styles from '@/assets/styles/fp.module.css' +import { projektMelodyEpoch } from "@/lib/constants"; +import add from "date-fns/add"; +import sub from "date-fns/sub"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCheckCircle, faPaperPlane, faXmark } from "@fortawesome/free-solid-svg-icons"; +import { useForm, useFieldArray, ValidationMode } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import * as Yup from 'yup'; + + +interface IUploadFormProps { + vtubers: IVtuber[]; +} + +interface IValidationResults { + valid: boolean; + issues: string[] | null; +} + +interface IFormSchema extends Yup.InferType { }; + + +const validationSchema = Yup.object().shape({ + vtuber: Yup.number() + .required('VTuber is required'), + date: Yup.date() + .typeError('Invalid date') // https://stackoverflow.com/a/72985532/1004931 + .min(sub(projektMelodyEpoch, { days: 1 }), 'Date must be after February 7 2020') + .max(add(new Date(), { days: 1 }), 'Date cannot be in the future') + .required('Date is required'), + notes: Yup.string().optional(), + attribution: Yup.boolean().optional(), + files: Yup.array() + .of( + Yup.object().shape({ + key: Yup.string().required('key is required'), + uploadId: Yup.string().required('uploadId is required') + }), + ) + .min(1, 'At least one file is required'), +}); + + + +export default function UploadForm({ vtubers }: IUploadFormProps) { + const searchParams = useSearchParams(); + const cuid = searchParams.get('cuid'); + const uppy = useContext(UppyContext); + const { authData } = useAuth(); + + const formOptions = { + resolver: yupResolver(validationSchema), + mode: 'onChange' as keyof ValidationMode, + }; + const { + register, + handleSubmit, + formState: { + errors, + isValid + }, + setValue, + watch, + } = useForm(formOptions); + + + const files = watch('files'); + + + + async function createUSC(data: IFormSchema) { + const res = await fetch(`${process.env.NEXT_PUBLIC_STRAPI_URL}/api/user-submitted-contents/createFromUppy`, { + method: 'POST', + headers: { + 'authorization': `Bearer ${authData?.accessToken}`, + 'content-type': 'application/json', + 'accept': 'application/json' + }, + body: JSON.stringify({ + data: { + files: data.files, + attribution: data.attribution, + notes: data.notes, + vtuber: data.vtuber, + date: data.date + } + }) + }); + + if (!res.ok) { + console.error('failed to fetch /api/user-submitted-contents/createFromUppy'); + } + } + + + uppy.on('complete', async (result: any) => { + let files = result.successful.map((f: any) => ({ key: f.s3Multipart.key, uploadId: f.s3Multipart.uploadId })); + setValue('files', files); + }); + + return ( + <> + +
+

Upload VOD

+ +

Together we can archive all lewdtuber livestreams!

+ + {(!authData?.accessToken) + ? + <> + + + + : ( + + + +
+
createUSC(data))}> + + +
+
+
+

+ Step 1 +

+

+ Upload the file +

+
+
+
+ + + + {errors.files &&

{errors.files.message?.toString()}

} + +
+
+ +
+ {/* {(!cuid) && } */} + +
+
+

+ Step 2 +

+

+ Tell us about the VOD +

+
+
+ +
+ + + + + + +
+ +
+ +
+

Choose the VTuber this VOD belongs to. (More VTubers will be added when storage/bandwidth funding is secured.)

+ {errors.vtuber &&

vtuber error

} + +
+ +
+ + setDate(evt.target.value)} + > +

The date when the VOD was originally streamed.

+ {errors.date &&

{errors.date.message?.toString()}

} + +
+ +
+ + +

If there are any issues with the VOD, put a note here. If there are no VOD issues, leave this field blank.

+
+ +
+ + +
+ +
+ +
+ + +
+
+
+

+ Step 3 +

+

+ Send the form +

+
+
+
+ + + +
+ + + + Step 1, File Upload +
+ +
+ + + + Step 2, Metadata +
+ + + + {/*

{message}

} + /> */} + + {/* {fields.map((field, index) => ( +
+ {' '} + +
+ ))} */} + + {/* { + JSON.stringify({ + touchedFields: Object.keys(touchedFields), + errors: Object.keys(errors) + }, null, 2) + } */} + + {/* setError('date', { type: 'custom', message: 'custom message' }); */} + + + + + + +
+
+ +
+
+ + + ) + } + +
+ + + ) + +} diff --git a/packages/next/app/components/user-controls.tsx b/packages/next/app/components/user-controls.tsx new file mode 100644 index 0000000..8fa4b8a --- /dev/null +++ b/packages/next/app/components/user-controls.tsx @@ -0,0 +1,229 @@ +'use client'; + +import React, { useState } from 'react'; +import { LogoutButton, useAuth } from "../components/auth" +import { patreonQuantumSupporterId, strapiUrl } from '../lib/constants'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faSave, faTimes, faCheck } from "@fortawesome/free-solid-svg-icons"; +import Skeleton from 'react-loading-skeleton'; + +interface IArchiveSupporterProps { + isNamePublic: boolean; + setIsNamePublic: Function; +} + +interface ISaveButtonProps { + isDirty: boolean; + isLoading: boolean; + isSuccess: boolean; + isNamePublic: boolean; + isLinkPublic: boolean; + vanityLink: string; + setVanityLink: Function; + setIsLoading: Function; + setIsSuccess: Function; + setIsDirty: Function; + setAuthData: Function; + errors: String[]; + setErrors: Function; +} + +interface IQuantumSupporterProps { + isLinkPublic: boolean; + hasUrlBenefit: boolean; + setIsLinkPublic: Function; + vanityLink: string; + setVanityLink: Function; +} + + +export default function UserControls() { + const [isLoading, setIsLoading] = useState(false); + const [isSuccess, setIsSuccess] = useState(false); + const [isDirty, setIsDirty] = useState(false); + const [isNamePublic, setIsNamePublic] = useState(false); + const [isLinkPublic, setIsLinkPublic] = useState(false); + const [errors, setErrors] = useState([]) + const [vanityLink, setVanityLink] = useState('') + + const { authData, setAuthData } = useAuth() + + + if (!authData) return

Loading...

+ + + const hasUrlBenefit = (authData?.user?.patreonBenefits) ? authData.user.patreonBenefits.split(' ').includes(patreonQuantumSupporterId) : false; + + return ( +
+
+

Patron Perks

+ + + + + + +
+
+ ); +}; + + +export function SaveButton({ + isDirty, + setIsDirty, + isLoading, + setIsLoading, + setIsSuccess, + isSuccess, + isNamePublic, + isLinkPublic, + vanityLink, + setVanityLink, + setAuthData, + errors, + setErrors, +}: ISaveButtonProps) { + const { authData } = useAuth(); + const handleClick = async () => { + if (!authData?.user) return; + try { + setIsLoading(true); + + const response = await fetch(`${strapiUrl}/api/profile/${authData.user.id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${authData.accessToken}` + }, + body: JSON.stringify({ + isNamePublic, + isLinkPublic, + vanityLink + }) + }); + + setIsLoading(false); + setIsDirty(true); + + if (!response.ok) { + setIsSuccess(false); + } else { + setIsSuccess(true); + + // Update authData if needed + const updatedAuthData = { ...authData }; + if (!updatedAuthData?.user) return; + updatedAuthData.user.vanityLink = vanityLink; + updatedAuthData.user.isNamePublic = isNamePublic; + updatedAuthData.user.isLinkPublic = isLinkPublic; + setAuthData(updatedAuthData); + } + } catch (error) { + if (error instanceof Error) { + setErrors(errors.concat([error.message])) + } + } + }; + + + return ( + + ) +} + +export function Thanks() { + return

Thank you so much for supporting Futureporn!

+} + +export function QuantumSupporterPerks({ isLinkPublic, setIsLinkPublic, setVanityLink, vanityLink, hasUrlBenefit }: IQuantumSupporterProps) { + const { authData } = useAuth() + + return ( +
+ +
+ +
+
+ setVanityLink(e.target.value)} + /> +
+ +
+ ) +} + +export function AdvancedArchiveSupporterPerks() { + +} + +export function ArchiveSupporterPerks({ isNamePublic, setIsNamePublic }: IArchiveSupporterProps) { + const { authData } = useAuth() + + return ( +
+ +
+ +
+
+ ) +} \ No newline at end of file diff --git a/packages/next/app/components/video-context.tsx b/packages/next/app/components/video-context.tsx new file mode 100644 index 0000000..4281a25 --- /dev/null +++ b/packages/next/app/components/video-context.tsx @@ -0,0 +1,56 @@ + +import VideoApiElement from "@mux/mux-player/dist/types/video-api"; +import { MutableRefObject, createContext, useState } from "react"; +import { ITagVodRelation } from "@/lib/tag-vod-relations"; + +export interface IVideoContextValue { + timeStamp: number; + setTimeStamp: Function; + tvrs: ITagVodRelation[]; + setTvrs: Function; +} + +// const defaultContextValue = { +// timeStamp: 3, +// setTimeStamp: () => null, +// ref: null, +// } + +export const VideoContext = createContext({} as IVideoContextValue); + + +// export function VideoContextProvider({ children }: IAuthContextProps): React.JSX.Element { +// const { value: authData, set: setAuthData } = useLocalStorageValue('authData', { +// defaultValue: null, +// }); + +// const { value: lastVisitedPath, set: setLastVisitedPath } = useLocalStorageValue('lastVisitedPath', { +// defaultValue: '/profile', +// initializeWithValue: false, +// }); +// const router = useRouter(); + +// const login = async () => { +// const currentPath = window.location.pathname; +// setLastVisitedPath(currentPath); +// router.push(`${strapiUrl}/api/connect/patreon`); +// }; + +// const logout = () => { +// setAuthData({ accessToken: null, user: null }); +// }; + +// return ( +// +// {children} +// +// ); +// } \ No newline at end of file diff --git a/packages/next/app/components/video-interactive.tsx b/packages/next/app/components/video-interactive.tsx new file mode 100644 index 0000000..28617d3 --- /dev/null +++ b/packages/next/app/components/video-interactive.tsx @@ -0,0 +1,134 @@ +'use client'; + +import { IVod } from "@/lib/vods"; +import { useRef, useState, useEffect, useCallback } from "react"; +import { VideoPlayer } from "./video-player"; +import { Tagger } from './tagger'; +import { ITimestamp, getTimestampsForVod } from "@/lib/timestamps"; +import { TimestampsList } from "./timestamps-list"; +import { ITagVodRelation } from "@/lib/tag-vod-relations"; +import { VideoContext } from "./video-context"; +import { getVodTitle } from "./vod-page"; +import { useSearchParams } from 'next/navigation'; +import VideoApiElement from "@mux/mux-player/dist/types/video-api"; +import { parseUrlTimestamp } from "@/lib/dates"; +import { faTags, faNoteSticky, faClock } from "@fortawesome/free-solid-svg-icons"; +import { Tag } from './tag'; +import VodNav from './vod-nav'; +import LinkableHeading from "./linkable-heading"; + + +export interface IVideoInteractiveProps { + vod: IVod; +} + + +function secondsToHumanReadable(timestampInSeconds: number): string { + const hours = Math.floor(timestampInSeconds / 3600); + const minutes = Math.floor((timestampInSeconds % 3600) / 60); + const seconds = timestampInSeconds % 60; + + return `${hours}h${minutes}m${seconds}s`; +} + + +function humanReadableTimestampToSeconds(timestamp: string): number | null { + const parts = timestamp.split(':'); + + if (parts.length !== 3) { + // Invalid format, return null or throw an error as appropriate + return null; + } + + const hours = parseInt(parts[0], 10); + const minutes = parseInt(parts[1], 10); + const seconds = parseInt(parts[2], 10); + + if (isNaN(hours) || isNaN(minutes) || isNaN(seconds)) { + // Invalid numeric values, return null or throw an error as appropriate + return null; + } + + const totalSeconds = hours * 3600 + minutes * 60 + seconds; + + return totalSeconds; +} + + + + +export function VideoInteractive({ vod }: IVideoInteractiveProps): React.JSX.Element { + + const [timeStamp, setTimeStamp] = useState(0); + const [tvrs, setTvrs] = useState([]); + const [isPlayerReady, setIsPlayerReady] = useState(false); + const [timestamps, setTimestamps] = useState([]); + const [currentTsPage, setCurrentTsPage] = useState(1); + + const getTimestampPage = useCallback(async (page: number) => { + const timestamps = await getTimestampsForVod(vod.id, page); + setTimestamps(timestamps); + }, [vod.id, setTimestamps]); // IGNORE TS LINTER! DO NOT PUT timestamps HERE! IT CAUSES SELF-DDOS! + + const ref = useRef(null); + const searchParams = useSearchParams(); + const t = searchParams.get('t'); + + + + useEffect(() => { + getTimestampPage(currentTsPage); + }, [vod.id, getTimestampPage, currentTsPage]); + + useEffect(() => { + if (!t) return; + if (!ref?.current) return; + const videoRef = ref.current as VideoApiElement; + const seconds = parseUrlTimestamp(t) + if (seconds === null) return; + videoRef.currentTime = seconds; + }, [t, isPlayerReady, ref]) + + + return ( + + + +

+ {getVodTitle(vod)} +

+ + +
+ {vod.attributes.note && ( + <> + +
{vod.attributes.note}
+ + )} + + + +
+ {vod.attributes.tagVodRelations.data.length === 0 &&

This vod has no tags

} + {vod.attributes.tagVodRelations.data.map((tvr: ITagVodRelation) => ( + + ))} + +
+ + +
+ +
+ ) +} \ No newline at end of file diff --git a/packages/next/app/components/video-player.tsx b/packages/next/app/components/video-player.tsx new file mode 100644 index 0000000..25bd69d --- /dev/null +++ b/packages/next/app/components/video-player.tsx @@ -0,0 +1,151 @@ +'use client'; + +import { useEffect, useState, forwardRef, useContext, Ref } from 'react'; +import { IVod } from '@/lib/vods'; +import "plyr-react/plyr.css"; +import { useAuth } from '@/components/auth'; +import { getVodTitle } from './vod-page'; +import { VideoSourceSelector } from '@/components/video-source-selector' +import { buildIpfsUrl } from '@/lib/ipfs'; +import { strapiUrl } from '@/lib/constants'; +import MuxPlayer from '@mux/mux-player-react/lazy'; +import { VideoContext } from './video-context'; +import MuxPlayerElement from '@mux/mux-player'; +import VideoApiElement from "@mux/mux-player/dist/types/video-api"; + +interface IPlayerProps { + vod: IVod; + setIsPlayerReady: Function; +} + +interface ITokens { + playbackToken: string; + storyboardToken: string; + thumbnailToken: string; +} + +async function getMuxPlaybackTokens(playbackId: string, jwt: string): Promise { + const res = await fetch(`${strapiUrl}/api/mux-asset/secure?id=${playbackId}`, { + headers: { + 'Authorization': `Bearer ${jwt}` + } + }) + const json = await res.json() + + return { + playbackToken: json.playbackToken, + storyboardToken: json.storyboardToken, + thumbnailToken: json.thumbnailToken + } +} + +function hexToRgba(hex: string, alpha: number) { + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + return `rgba(${r}, ${g}, ${b}, ${alpha})`; +} + + + +export const VideoPlayer = forwardRef(function VideoPlayer( props: IPlayerProps, ref: Ref ): React.JSX.Element { + const { vod, setIsPlayerReady } = props + const title: string = getVodTitle(vod); + const { authData } = useAuth(); + const [selectedVideoSource, setSelectedVideoSource] = useState(''); + const [isEntitledToCDN, setIsEntitledToCDN] = useState(false); + const [hlsSource, setHlsSource] = useState(''); + const [isClient, setIsClient] = useState(false); + const [playbackId, setPlaybackId] = useState(''); + const [src, setSrc] = useState(''); + const [tokens, setTokens] = useState({}); + const { setTimeStamp } = useContext(VideoContext); + + + + useEffect(() => { + setIsClient(true); + const token = authData?.accessToken; + const playbackId = vod?.attributes.muxAsset?.data?.attributes?.playbackId; + + if (token) setIsEntitledToCDN(true); + + if (selectedVideoSource === 'Mux') { + if (!!token && !!playbackId) { + try { + getMuxPlaybackTokens(vod.attributes.muxAsset.data.attributes.playbackId, token) + .then((tokens) => { + setTokens({ + playback: tokens.playbackToken, + storyboard: tokens.storyboardToken, + thumbnail: tokens.thumbnailToken + }) + setHlsSource(vod.attributes.muxAsset.data.attributes.playbackId) + setPlaybackId(vod.attributes.muxAsset.data.attributes.playbackId) + }); + } + + catch (e) { + console.error(e) + } + } + } else if (selectedVideoSource === 'B2') { + if (!vod.attributes.videoSrcB2) return; // This shouldn't happen because videoSourceSelector won't choose B2 if there is no b2. This return is only for satisfying TS + setHlsSource(vod.attributes.videoSrcB2.data.attributes.cdnUrl); + setPlaybackId(''); + setSrc(vod.attributes.videoSrcB2.data.attributes.cdnUrl); + } else if (selectedVideoSource === 'IPFSSource') { + setHlsSource(''); + setPlaybackId(''); + setSrc(buildIpfsUrl(vod.attributes.videoSrcHash)) + } else if (selectedVideoSource === 'IPFS240') { + setHlsSource(''); + setPlaybackId(''); + setSrc(buildIpfsUrl(vod.attributes.video240Hash)) + } + }, [selectedVideoSource, authData, vod, setHlsSource]); + + + if (!isClient) return <> + + + return ( + <> + { + setIsPlayerReady(true)} + } + ref={ref} + preload="auto" + crossOrigin="*" + loading="viewport" + playbackId={playbackId} + src={src} + tokens={tokens} + primaryColor="#FFFFFF" + secondaryColor={hexToRgba(vod.attributes.vtuber.data.attributes.themeColor, 0.85)} + metadata={{ + video_title: getVodTitle(vod) + }} + + streamType="on-demand" + onTimeUpdate={(evt) => { + const muxPlayer = evt.target as VideoApiElement + const { currentTime } = muxPlayer; + setTimeStamp(currentTime) + }} + muted + > + + + + ) +}) \ No newline at end of file diff --git a/packages/next/app/components/video-source-selector.tsx b/packages/next/app/components/video-source-selector.tsx new file mode 100644 index 0000000..5f011a9 --- /dev/null +++ b/packages/next/app/components/video-source-selector.tsx @@ -0,0 +1,130 @@ +'use client'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import { faPatreon } from "@fortawesome/free-brands-svg-icons"; +import { faGlobe } from "@fortawesome/free-solid-svg-icons"; +import { useState, useEffect } from 'react'; + +interface IVSSProps { + isMux: boolean; + isB2: boolean; + isIPFSSource: boolean; + isIPFS240: boolean; + isEntitledToCDN: boolean; + setSelectedVideoSource: (option: string) => void; + selectedVideoSource: string; +} + +export function VideoSourceSelector({ + isMux, + isB2, + isIPFSSource, + isIPFS240, + isEntitledToCDN, + selectedVideoSource, + setSelectedVideoSource, +}: IVSSProps): React.JSX.Element { + + // Check for user's entitlements and saved preference when component mounts + useEffect(() => { + // Function to determine the best video source based on entitlements and preferences + const determineBestVideoSource = () => { + if (isEntitledToCDN) { + if (selectedVideoSource === 'Mux' && isMux) { + return 'Mux'; + } else if (selectedVideoSource === 'B2' && isB2) { + return 'B2'; + } + } + // If the user doesn't have entitlements or their preference is not available, default to IPFS + if (isIPFSSource) { + return 'IPFSSource'; + } else if (isIPFS240) { + return 'IPFS240'; + } + // If no sources are available, return an empty string + return ''; + }; + + // If selectedVideoSource is unset, find the value to use + if (selectedVideoSource === '') { + // Load the user's saved preference from storage (e.g., local storage) + const savedPreference = localStorage.getItem('videoSourcePreference'); + + // Check if the saved preference is valid based on entitlements and available sources + if (savedPreference === 'Mux' && isMux && isEntitledToCDN) { + setSelectedVideoSource('Mux'); + } else if (savedPreference === 'B2' && isB2 && isEntitledToCDN) { + setSelectedVideoSource('B2'); + } else { + // Determine the best video source if the saved preference is invalid or not available + const bestSource = determineBestVideoSource(); + setSelectedVideoSource(bestSource); + } + } + + + }, [isMux, isB2, isIPFSSource, isIPFS240, isEntitledToCDN, selectedVideoSource, setSelectedVideoSource]); + + // Handle button click to change the selected video source + const handleSourceClick = (source: string) => { + if ( + (source === 'Mux' && isMux && isEntitledToCDN) || + (source === 'B2' && isB2 && isEntitledToCDN) || + (source === 'IPFSSource') || + (source === 'IPFS240') + ) { + setSelectedVideoSource(source); + // Save the user's preference to storage (e.g., local storage) + localStorage.setItem('videoSourcePreference', source); + } + }; + + return ( + <> +
+ +
+ + ) +} \ No newline at end of file diff --git a/packages/next/app/components/vod-card.tsx b/packages/next/app/components/vod-card.tsx new file mode 100644 index 0000000..175ed40 --- /dev/null +++ b/packages/next/app/components/vod-card.tsx @@ -0,0 +1,72 @@ +import Link from "next/link"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faPatreon } from "@fortawesome/free-brands-svg-icons"; +import { faVideo } from "@fortawesome/free-solid-svg-icons"; +import { getSafeDate, getDateFromSafeDate } from '@/lib/dates'; +import { IVtuber } from '@/lib/vtubers'; +import Image from 'next/image' +import { LocalizedDate } from '@/components/localized-date' +import { IMuxAsset, IMuxAssetResponse } from "@/lib/types"; +import { IB2File } from "@/lib/b2File"; + +interface IVodCardProps { + id: number; + title: string; + date: string; + muxAsset: string | undefined; + thumbnail: string | undefined; + vtuber: IVtuber; +} + + +export default function VodCard({id, title, date, muxAsset, thumbnail = 'https://futureporn-b2.b-cdn.net/default-thumbnail.webp', vtuber}: IVodCardProps) { + + if (!vtuber?.attributes?.slug) return

VOD {id} is missing VTuber

+ + return ( +
+
+ +
+
+ {title} +
+
+
+

{title}

+ + +
+
+ +
+ {muxAsset && ( +
+ +
+ )} +
+
+ +
+
+ + ) + } + + + + + diff --git a/packages/next/app/components/vod-nav.tsx b/packages/next/app/components/vod-nav.tsx new file mode 100644 index 0000000..c760ed1 --- /dev/null +++ b/packages/next/app/components/vod-nav.tsx @@ -0,0 +1,90 @@ +'use client'; + +import { faVideo, faExternalLinkAlt, faShareAlt } from "@fortawesome/free-solid-svg-icons"; +import { faXTwitter } from '@fortawesome/free-brands-svg-icons'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import Image from 'next/image'; +import Link from 'next/link'; +import { IVod } from '@/lib/vods'; +import { buildIpfsUrl } from '@/lib/ipfs'; +import { getSafeDate } from "@/lib/dates"; +import { StreamButton } from '@/components/stream-button'; +import VtuberButton from "./vtuber-button"; + +export function getDownloadLink(cid: string, safeDate: string, slug: string, quality: string) { + return buildIpfsUrl(`${cid}?filename=${slug}-${safeDate}-${quality}.mp4`) +} + + +export interface IVodNavProps { + vod: IVod; +} + +export default function VodNav ({ vod }: IVodNavProps) { + const safeDate = getSafeDate(vod.attributes.date2); + return ( + + ) +} \ No newline at end of file diff --git a/packages/next/app/components/vod-page.tsx b/packages/next/app/components/vod-page.tsx new file mode 100644 index 0000000..9d81faa --- /dev/null +++ b/packages/next/app/components/vod-page.tsx @@ -0,0 +1,96 @@ +import { getUrl, getNextVod, getPreviousVod, getLocalizedDate } from '@/lib/vods'; +import { IVod } from '@/lib/vods'; +import Link from 'next/link'; +import { VideoInteractive } from './video-interactive'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faChevronLeft, faChevronRight, faGlobe, faLink } from "@fortawesome/free-solid-svg-icons"; +import { notFound } from 'next/navigation'; +import { IpfsCid } from './ipfs-cid'; +import LinkableHeading from './linkable-heading'; + + +export function getVodTitle(vod: IVod): string { + return vod.attributes.title || vod.attributes.announceTitle || `${vod.attributes.vtuber.data.attributes.displayName} ${vod.attributes.date2}`; +} + +export function buildMuxUrl(playbackId: string, token: string) { + return `https://stream.mux.com/${playbackId}.m3u8?token=${token}` +} + +export function buildMuxSignedPlaybackId(playbackId: string, token: string) { + return `${playbackId}?token=${token}` +} + +export function buildMuxThumbnailUrl(playbackId: string, token: string) { + return `https://image.mux.com/${playbackId}/storyboard.vtt?token=${token}` +} + + +export default async function VodPage({vod}: { vod: IVod }) { + + if (!vod) notFound(); + const slug = vod.attributes.vtuber.data.attributes.slug; + const previousVod = await getPreviousVod(vod); + const nextVod = await getNextVod(vod); + + + return ( + +
+
+
+ + + {(vod.attributes.videoSrcHash || vod.attributes.video240Hash) && ( + <> + + {vod.attributes.videoSrcHash && ( + + )} + {vod.attributes.video240Hash && ( + + )} + + )} + + + + + +
+
+
+ ); +} diff --git a/packages/next/app/components/vods-list.tsx b/packages/next/app/components/vods-list.tsx new file mode 100644 index 0000000..1eb8b96 --- /dev/null +++ b/packages/next/app/components/vods-list.tsx @@ -0,0 +1,66 @@ +import React from 'react' +import Link from 'next/link'; +import VodCard from './vod-card'; +import { IVtuber, IVtuberResponse } from '@/lib/vtubers'; +import { IVodsResponse, IVod } from '@/lib/vods'; +import { getVodTitle } from './vod-page'; +import { notFound } from 'next/navigation'; + +interface IVodsListProps { + vtuber?: IVtuber; + vods: IVod[]; + page: number; + pageSize: number; +} + + +interface IVodsListHeadingProps { + slug: string; + displayName: string; +} + +export function VodsListHeading({ slug, displayName }: IVodsListHeadingProps): React.JSX.Element { + return ( +
+

+ {displayName} Vods +

+
+ ) +} + + +export default function VodsList({ vods, page = 1, pageSize = 24 }: IVodsListProps): React.JSX.Element { + // if (!vtuber) return
vtuber is not defined. vtuber:{JSON.stringify(vtuber, null, 2)}
+ // if (!vods) return
failed to load vods
; + if (!vods) return notFound() + + // @todo [x] pagination + // @todo [x] sortability + return ( + <> + {/*

VodsList on page {page}, pageSize {pageSize}, with {vods.data.length} vods

*/} + + {/*
+                
+                    {JSON.stringify(vods.data, null, 2)}
+                
+            
*/} + + +
+ {vods.map((vod: IVod) => ( + + ))} +
+ + ); +} diff --git a/packages/next/app/components/vtuber-button.tsx b/packages/next/app/components/vtuber-button.tsx new file mode 100644 index 0000000..6826a28 --- /dev/null +++ b/packages/next/app/components/vtuber-button.tsx @@ -0,0 +1,29 @@ +import Image from "next/image" + +interface VtuberButtonProps { + image: string; + displayName: string; + size?: string; +} + +export default function VtuberButton ({ image, displayName, size }: VtuberButtonProps) { + const sizeClass = (() => { + if (size === 'large') return 'is-large'; + if (size === 'medium') return 'is-medium'; + if (size === 'small') return 'is-small' + })(); + return ( +
+ + {displayName} + + {displayName} +
+ ); +} \ No newline at end of file diff --git a/packages/next/app/components/vtuber-card.tsx b/packages/next/app/components/vtuber-card.tsx new file mode 100644 index 0000000..a324dc2 --- /dev/null +++ b/packages/next/app/components/vtuber-card.tsx @@ -0,0 +1,43 @@ +import Link from "next/link"; +import type { IVtuber } from '@/lib/vtubers'; +import { getVodsForVtuber } from "@/lib/vods"; +import Image from 'next/image' +import NotFound from "app/vt/[slug]/not-found"; +import ArchiveProgress from "./archive-progress"; + +export default async function VTuberCard(vtuber: IVtuber) { + const { id, attributes: { slug, displayName, imageBlur, image }} = vtuber; + if (!imageBlur) return

this is a vtubercard with an invalid imageBlur={imageBlur}

+ const vods = await getVodsForVtuber(id) + if (!vods) return + return ( + +
+
+
+
+
+ {displayName} +
+
+
+

{displayName}

+ +
+
+
+
+ + ) + } \ No newline at end of file diff --git a/packages/next/app/connect/patreon/redirect/page.tsx b/packages/next/app/connect/patreon/redirect/page.tsx new file mode 100644 index 0000000..382ad44 --- /dev/null +++ b/packages/next/app/connect/patreon/redirect/page.tsx @@ -0,0 +1,123 @@ +'use client' + +import { useSearchParams, useRouter } from 'next/navigation' +import Link from 'next/link' +import { useEffect, useState } from 'react' +import { strapiUrl } from '@/lib/constants' +import { useAuth, IAuthData, IUser, IJWT } from '@/components/auth' +import { DangerNotification } from '@/components/notifications' + +export type AccessToken = string | null; + + +export default function Page() { + const searchParams = useSearchParams() + const router = useRouter() + const { authData, setAuthData, lastVisitedPath } = useAuth() + const [errors, setErrors] = useState([]) + + const initAuth = async () => { + try { + const accessToken: AccessToken = getAccessTokenFromURL(); + const json = await getJwt(accessToken); + if (!json) { + setErrors(errors.concat(['Unable to get access token from portal. Please try again later or check Futureporn Discord.'])) + } else { + storeJwtJson(json) + redirect(); + } + } catch (error) { + console.error(error); + } + }; + + const storeJwtJson = (json: IJWT) => { + + + // Store the JWT and other relevant data in your state management system + const data: IAuthData = { + accessToken: json.jwt, + user: json.user, + } + setAuthData(data); + } + + + const getAccessTokenFromURL = () => { + const accessToken: AccessToken = searchParams?.get('access_token'); + if (!accessToken) { + throw new Error('Failed to get access_token from auth portal.'); + } + return accessToken; + }; + + const getJwt = async (accessToken: AccessToken): Promise => { + + try { + const response = await fetch(`${strapiUrl}/api/auth/patreon/callback?access_token=${accessToken}`); + + if (!response.ok) { + // Handle non-2xx HTTP response status + throw new Error(`Failed to fetch. Status: ${response.status}`); + } + + const json = await response.json(); + + if (!json.jwt) { + throw new Error('Failed to get auth token. Please try again later.'); + } + + return json; + } catch (error) { + console.error(error); + return null; // Return null or handle the error in an appropriate way + } + }; + + + const redirect = () => { + if (!lastVisitedPath) return; // on first render, it's likely null + router.push(lastVisitedPath); + }; + + + useEffect(() => { + initAuth() + }) + + + + + + + {/* + After user auths, + they are redirected to this page. + + This page grabs the access_token from the query string, + exchanges it with strapi for a jwt + then persists the jwt + + After a jwt is stored, this page redirects the user + to whatever page they were previously on. + */} + + // @todo get query parameters + // @todo save account info to session + // @todo ??? + // @todo profit + // const searchParams = useSearchParams() + // const accessToken = searchParams?.get('access_token'); + // const refreshToken = searchParams?.get('refresh_token'); + // const lastVisitedPath = '@todo!' + + return ( +
+ {errors && errors.length > 0 && ( + + )} +

Redirecting...

+ Click here if you are not automatically redirected +
+ ) +} \ No newline at end of file diff --git a/packages/next/app/faq/page.tsx b/packages/next/app/faq/page.tsx new file mode 100644 index 0000000..6caeca4 --- /dev/null +++ b/packages/next/app/faq/page.tsx @@ -0,0 +1,104 @@ +import Link from 'next/link'; +import { getVtuberBySlug } from '../lib/vtubers' +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons"; +import { faLink } from '@fortawesome/free-solid-svg-icons'; +import { projektMelodyEpoch } from '@/lib/constants'; +import LinkableHeading from '@/components/linkable-heading'; + +export default async function Page() { + return ( +
+
+
+

Frequently Asked Questions (FAQ)

+ + + +
+ +

VTuber is a portmantou of the words Virtual and Youtuber. Originally started in Japan, VTubing uses cameras and/or motion capture technology to replicate human movement and facial expressions onto a virtual character in realtime.

+
+ +
+ +

Lewdtubers are sexually explicit vtubers. ProjektMelody was the first Vtuber to livestream on Chaturbate on {projektMelodyEpoch.toDateString()}. Many more followed after her.

+
+ +
+ +

Interplanetary File System (IPFS) is a new-ish technology which gives a unique address to every file. This address is called a Content ID, or CID for short. A CID can be used to request the file from the IPFS network.

+

IPFS is a distributed, decentralized protocol with no central point of failure. IPFS provider nodes can come and go, providing file serving capacity to the network. As long as there is at least one node pinning the content you want, you can download it.

+

There are a few ways to use IPFS, each with their own tradeoffs. Firstly, you can use a public gateway. IPFS public gateways can be overloaded and unreliable at times, but it's simple to use. All you have to do is visit a gateway URL containing the CID. One such example is https://ipfs.io/ipfs/bafkreigaknpexyvxt76zgkitavbwx6ejgfheup5oybpm77f3pxzrvwpfdi

+

The next way to use IPFS consists of running IPFS Desktop on your computer. A local IPFS node runs for as long as IPFS Desktop is active, and you can query this node for the content you want. This setup works best with IPFS Companion, or a web browser that natively supports IPFS, such as Brave browser.

+
+ + + +
+
+
+ +
+
+

You may get an error when clicking on a video link. Errors such as DNS_PROBE_FINISHED_NXDOMAIN

+ +

This is a DNS server error that occurs when a web browser isn't able to translate the domain name into an IP address.

+ +

If this happens, using a different DNS server can fix it. There are many gratis services to choose from, including Cloudflare DNS or Google DNS.

+ +

Often, using a DNS server other than the one provided to you by your ISP can improve your internet browsing experience for all websites.

+
+
+
+ +
+
+
+ +
+ +
+

Bandwidth is prohibitively expensive, so that's the free-to-play experience at the moment. (Patrons get access to CDN which is much faster.)

+

If the video isn't loading fast enough to stream, you can download the entire video then playback later on your device.

+
+
+
+ +
+
+
+ + +

Yes! The recommended way is to use either IPFS Desktop or ipget.

+

ipget example is as follows.

+
+                  
+                    ipget --progress -o projektmelody-chaturbate-2023-12-03.mp4 bafybeiejms45zzonfe7ndr3mp4vmrqrg3btgmuche3xkeq5b77uauuaxkm
+                  
+                
+
+
+
+ +
+
+ +
+

Yes. Futureporn aims to become the galaxy's best VTuber hentai site.

+
+ +
+
+ +

Bandwidth and rental fees are expensive, so Futureporn needs financial assistance to keep servers online and videos streaming.

+

Patrons gain access to perks like our video Content Delivery Network (CDN), and optional shoutouts on the patrons page.

+

Additionally, help is needed populating our archive with vods from past lewdtuber streams.

+
+
+ +
+
+
+ ) +} \ No newline at end of file diff --git a/packages/next/app/favicon.ico b/packages/next/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2ed11c7480c90fcd2c32df0465864598baa02c05 GIT binary patch literal 318 zcmbV|I}U&_3_~4>12_v}3uBMTy&1qML +
+
+
+ +

RSS Feed

+ +

Keep up to date with new VODs using Real Simple Syndication (RSS).

+ +

Don't have a RSS reader? Futureporn recommends Fraidycat

+ +
+

ATOM

+

RSS

+

JSON

+
+
+
+
+ + ) +} + diff --git a/packages/next/app/feed/rss.xml/route.ts b/packages/next/app/feed/rss.xml/route.ts new file mode 100644 index 0000000..f8b4747 --- /dev/null +++ b/packages/next/app/feed/rss.xml/route.ts @@ -0,0 +1,11 @@ +import { generateFeeds } from "@/lib/rss" + +export async function GET() { + const { rss2 } = await generateFeeds() + const options = { + headers: { + "Content-Type": "application/rss+xml" + } + } + return new Response(rss2, options) +} \ No newline at end of file diff --git a/packages/next/app/goals/page.tsx b/packages/next/app/goals/page.tsx new file mode 100644 index 0000000..4bf4f05 --- /dev/null +++ b/packages/next/app/goals/page.tsx @@ -0,0 +1,75 @@ +import { getGoals } from "@/lib/pm"; +import { getCampaign } from "@/lib/patreon"; + +interface IFundingStatusBadgeProps { + completedPercentage: number; +} + +function FundingStatusBadge({ completedPercentage }: IFundingStatusBadgeProps) { + if (completedPercentage === 100) return Funded; + return ( + + + {completedPercentage}% Funded + + + ); +} + + + +// export interface IGoals { +// complete: IIssue[]; +// inProgress: IIssue[]; +// planned: IIssue[]; +// featuredFunded: IIssue; +// featuredUnfunded: IIssue; +// } +export default async function Page() { + const { pledgeSum } = await getCampaign() + const goals = await getGoals(pledgeSum); + if (!goals) return

failed to get goals

+ const { inProgress, planned, complete } = goals; + return ( + <> +
+
+
+ +

Goals

+

+ In Progress +

+
    + {inProgress.map((goal) => ( +
  • + ☐ {goal.title} {(!!goal?.amountCents && !!goal.completedPercentage) && } +
  • + ))} +
+

+ Planned +

+
    + {planned.map((goal) => ( +
  • + ☐ {goal.title} {(!!goal?.amountCents && !!goal.completedPercentage) && } +
  • + ))} +
+

+ Completed +

+
    + {complete.map((goal) => ( +
  • + ✅ {goal.title} {(!!goal?.amountCents && !!goal.completedPercentage) && } +
  • + ))} +
+
+
+
+ + ) +} \ No newline at end of file diff --git a/packages/next/app/health/page.tsx b/packages/next/app/health/page.tsx new file mode 100644 index 0000000..657976e --- /dev/null +++ b/packages/next/app/health/page.tsx @@ -0,0 +1,12 @@ +import Tes from '@/assets/svg/tes'; + +export default async function Page() { + return ( +
+
+

Healthy!

+ +
+
+ ) +} \ No newline at end of file diff --git a/packages/next/app/latest-vods/[page]/page.tsx b/packages/next/app/latest-vods/[page]/page.tsx new file mode 100644 index 0000000..1c03ece --- /dev/null +++ b/packages/next/app/latest-vods/[page]/page.tsx @@ -0,0 +1,32 @@ +import VodsList from '@/components/vods-list'; +import { getVods } from '@/lib/vods'; +import Pager from '@/components/pager'; + +interface IPageParams { + params: { + page: number; + }; +} + +export default async function Page({ params: { page } }: IPageParams) { + let vods; + try { + vods = await getVods(page, 24, true); + } catch (error) { + console.error("An error occurred:", error); + return
Error: {JSON.stringify(error)}
; + } + + return ( + <> +

Latest VODs

+

page {page}

+ + + + ); +} diff --git a/packages/next/app/latest-vods/page.tsx b/packages/next/app/latest-vods/page.tsx new file mode 100644 index 0000000..2c1edc7 --- /dev/null +++ b/packages/next/app/latest-vods/page.tsx @@ -0,0 +1,24 @@ + +import VodsList from '@/components/vods-list'; +import { IVodsResponse } from '@/lib/vods'; +import Pager from '@/components/pager'; +import { getVods } from '@/lib/vods'; + +interface IPageParams { + params: { + slug: string; + } +} + +export default async function Page({ params }: IPageParams) { + const vods: IVodsResponse = await getVods(1, 24); + + return ( + <> +

Latest VODs

+

page 1

+ + + + ) +} \ No newline at end of file diff --git a/packages/next/app/layout.tsx b/packages/next/app/layout.tsx new file mode 100644 index 0000000..26d3b81 --- /dev/null +++ b/packages/next/app/layout.tsx @@ -0,0 +1,70 @@ +import { ReactNode } from 'react' +import Footer from "./components/footer" +import Navbar from "./components/navbar" +import "../assets/styles/global.sass"; +import "@fortawesome/fontawesome-svg-core/styles.css"; +import { AuthProvider } from './components/auth'; +import type { Metadata } from 'next'; +import NotificationCenter from './components/notification-center'; +import UppyProvider from './uppy'; +// import NextTopLoader from 'nextjs-toploader'; +// import Ipfs from './components/ipfs'; // slows down the page too much + + + +export const metadata: Metadata = { + title: 'Futureporn.net', + description: "The Galaxy's Best VTuber Hentai Site", + other: { + RATING: 'RTA-5042-1996-1400-1577-RTA' + }, + metadataBase: new URL('https://futureporn.net'), + twitter: { + site: '@futureporn_net', + creator: '@cj_clippy' + }, + alternates: { + types: { + 'application/atom+xml': '/feed/feed.xml', + 'application/rss+xml': '/feed/rss.xml', + 'application/json': '/feed/feed.json' + } + } +} + +type Props = { + children: ReactNode; +} + + +export default function RootLayout({ + children, +}: Props) { + return ( + + + {/* */} + + + + +
+ {children} +
+
+
+
+ + + ) +} diff --git a/packages/next/app/lib/b2File.ts b/packages/next/app/lib/b2File.ts new file mode 100644 index 0000000..876f3a8 --- /dev/null +++ b/packages/next/app/lib/b2File.ts @@ -0,0 +1,15 @@ +import { IMeta } from "./types"; + +export interface IB2File { + id: number; + attributes: { + url: string; + key: string; + uploadId: string; + cdnUrl: string; + } +} +export interface IB2FileResponse { + data: IB2File; + meta: IMeta; +} \ No newline at end of file diff --git a/packages/next/app/lib/blog.ts b/packages/next/app/lib/blog.ts new file mode 100644 index 0000000..222b7f6 --- /dev/null +++ b/packages/next/app/lib/blog.ts @@ -0,0 +1,5 @@ +export interface IBlogPost { + slug: string; + title: string; + id: number; +} \ No newline at end of file diff --git a/packages/next/app/lib/constants.ts b/packages/next/app/lib/constants.ts new file mode 100644 index 0000000..174bccd --- /dev/null +++ b/packages/next/app/lib/constants.ts @@ -0,0 +1,20 @@ +// export const strapiUrl = (process.env.NODE_ENV === 'production') ? 'https://portal.futureporn.net' : 'https://chisel.sbtp:1337' +// export const siteUrl = (process.env.NODE_ENV === 'production') ? 'https://futureporn.net' : 'http://localhost:3000' +export const siteUrl = process.env.NEXT_PUBLIC_SITE_URL +export const strapiUrl = process.env.NEXT_PUBLIC_STRAPI_URL +export const patreonSupporterBenefitId: string = '4760169' +export const patreonQuantumSupporterId: string = '10663202' +export const patreonVideoAccessBenefitId: string = '13462019' +export const skeletonHeight = '32pt' +export const skeletonBaseColor = '#000' +export const skeletonHighlightColor = '#000' +export const skeletonBorderRadius = 0 +export const description = "The Galaxy's Best VTuber Hentai Site" +export const title = "Futureporn.net" +export const siteImage = 'https://futureporn.net/images/futureporn-icon.png' +export const favicon = 'https://futureporn.net/favicon.ico' +export const authorName = 'CJ_Clippy' +export const authorEmail = 'cj@futureporn.net' +export const authorLink = 'https://futureporn.net' +export const giteaUrl = 'https://gitea.futureporn.net' +export const projektMelodyEpoch = new Date('2020-02-07T23:21:48.000Z') \ No newline at end of file diff --git a/packages/next/app/lib/contributors.ts b/packages/next/app/lib/contributors.ts new file mode 100644 index 0000000..e2e0788 --- /dev/null +++ b/packages/next/app/lib/contributors.ts @@ -0,0 +1,24 @@ +import { strapiUrl } from "./constants"; +import fetchAPI from "./fetch-api"; + +export interface IContributor { + id: number; + attributes: { + name: string; + url?: string; + isFinancialDonor: boolean; + isVodProvider: boolean; + } +} + + +export async function getContributors(): Promise { + try { + const res = await fetchAPI(`/contributors`); + return res.data; + } catch (e) { + console.error(`error while fetching contributors`) + console.error(e); + return null; + } +} diff --git a/packages/next/app/lib/dates.ts b/packages/next/app/lib/dates.ts new file mode 100644 index 0000000..5d7c8ca --- /dev/null +++ b/packages/next/app/lib/dates.ts @@ -0,0 +1,59 @@ +import { parse } from 'date-fns'; +import { format } from 'date-fns-tz' +import utcToZonedTime from 'date-fns-tz/utcToZonedTime' +import zonedTimeToUtc from 'date-fns-tz/zonedTimeToUtc' + +const safeDateFormatString: string = "yyyyMMdd'T'HHmmss'Z'" +const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; + + +export function getSafeDate(date: string | Date): string { + let dateString: string; + + if (typeof date === 'string') { + const dateObject = utcToZonedTime(date, 'UTC'); + dateString = format(dateObject, safeDateFormatString, { timeZone: 'UTC' }); + } else { + dateString = format(date, safeDateFormatString, { timeZone: 'UTC' }); + } + + return dateString; +} + + +export function getDateFromSafeDate(safeDate: string): Date { + const date = parse(safeDate, safeDateFormatString, new Date()) + const utcDate = zonedTimeToUtc(date, 'UTC') + return utcDate; +} + + +export function formatTimestamp(seconds: number = 0): string { + return new Date(seconds * 1000).toISOString().slice(11, 19); +} + +export function formatUrlTimestamp(timestampInSeconds: number): string { + const hours = Math.floor(timestampInSeconds / 3600); + const minutes = Math.floor((timestampInSeconds % 3600) / 60); + const seconds = timestampInSeconds % 60; + return `${hours}h${minutes}m${seconds}s`; +} + +export function parseUrlTimestamp(timestamp: string): number | null { + // Regular expression to match the "XhYmZs" format + const regex = /^(\d+)h(\d+)m(\d+)s$/; + const match = timestamp.match(regex); + + if (match) { + const hours = parseInt(match[1], 10); + const minutes = parseInt(match[2], 10); + const seconds = parseInt(match[3], 10); + + if (!isNaN(hours) && !isNaN(minutes) && !isNaN(seconds)) { + return hours * 3600 + minutes * 60 + seconds; + } + } + + // If the format doesn't match or parsing fails, return null + return null; +} \ No newline at end of file diff --git a/packages/next/app/lib/fetch-api.ts b/packages/next/app/lib/fetch-api.ts new file mode 100644 index 0000000..1130079 --- /dev/null +++ b/packages/next/app/lib/fetch-api.ts @@ -0,0 +1,34 @@ +// greets https://github.com/strapi/nextjs-corporate-starter/blob/main/frontend/src/app/%5Blang%5D/utils/fetch-api.tsx#L4 + +import qs from "qs"; +import { strapiUrl } from "./constants"; + +export default async function fetchAPI( + path: string, + urlParamsObject = {}, + options = {} +) { + try { + // Merge default and user options + const mergedOptions = { + next: { revalidate: 60 }, + headers: { + "Content-Type": "application/json", + }, + ...options, + }; + + // Build request URL + const queryString = qs.stringify(urlParamsObject); + const requestUrl = `${strapiUrl}/api${path}${queryString ? `?${queryString}` : ""}`; + + // Trigger API call + const response = await fetch(requestUrl, mergedOptions); + const data = await response.json(); + return data; + + } catch (error) { + console.error(error); + throw new Error(`Error while fetching data from API.`); + } +} \ No newline at end of file diff --git a/packages/next/app/lib/fetchers.ts b/packages/next/app/lib/fetchers.ts new file mode 100644 index 0000000..3e28468 --- /dev/null +++ b/packages/next/app/lib/fetchers.ts @@ -0,0 +1,32 @@ +import { strapiUrl } from "./constants"; + +export async function fetchPaginatedData(apiEndpoint: string, pageSize: number, queryParams: Record = {}): Promise { + let data: any[] = []; + let totalDataCount: number = 0; + let totalRequestsNeeded: number = 1; + + for (let requestCounter = 0; requestCounter < totalRequestsNeeded; requestCounter++) { + const humanReadableRequestCount = requestCounter + 1; + const params = new URLSearchParams({ + 'pagination[page]': humanReadableRequestCount.toString(), + 'pagination[pageSize]': pageSize.toString(), + ...queryParams, + }); + const url = `${strapiUrl}${apiEndpoint}?${params}`; + + const response = await fetch(url, { + method: 'GET' + }); + + const responseData = await response.json(); + + + if (requestCounter === 0) { + totalDataCount = responseData.meta.pagination.total; + totalRequestsNeeded = Math.ceil(totalDataCount / pageSize); + } + data = data.concat(responseData.data); + } + + return data; +} diff --git a/packages/next/app/lib/ipfs.ts b/packages/next/app/lib/ipfs.ts new file mode 100644 index 0000000..ffb3f04 --- /dev/null +++ b/packages/next/app/lib/ipfs.ts @@ -0,0 +1,7 @@ +export function buildIpfsUrl (urlFragment: string): string { + return `https://ipfs.io/ipfs/${urlFragment}` +} + +export function buildPatronIpfsUrl (cid: string, token: string): string { + return `https://gw.futureporn.net/ipfs/${cid}?token=${token}` +} diff --git a/packages/next/app/lib/patreon.ts b/packages/next/app/lib/patreon.ts new file mode 100644 index 0000000..420356c --- /dev/null +++ b/packages/next/app/lib/patreon.ts @@ -0,0 +1,55 @@ +import { strapiUrl, patreonVideoAccessBenefitId, giteaUrl } from './constants' +import { IAuthData } from '@/components/auth'; + +export interface IPatron { + username: string; + vanityLink?: string; +} + + +export interface ICampaign { + pledgeSum: number; + patronCount: number; +} + + +export interface IMarshalledCampaign { + data: { + attributes: { + pledge_sum: number, + patron_count: number + } + } +} + + + +export function isEntitledToPatronVideoAccess(authData: IAuthData): boolean { + if (!authData.user?.patreonBenefits) return false; + const patreonBenefits = authData.user.patreonBenefits + return (patreonBenefits.includes(patreonVideoAccessBenefitId)) +} + + +export async function getPatrons(): Promise { + const res = await fetch(`${strapiUrl}/api/patreon/patrons`); + return res.json(); +} + + +export async function getCampaign(): Promise { + const res = await fetch('https://www.patreon.com/api/campaigns/8012692', { + headers: { + accept: 'application/json' + }, + next: { + revalidate: 43200 // 12 hour cache + } + }) + const campaignData = await res.json(); + const data = { + patronCount: campaignData.data.attributes.patron_count, + pledgeSum: campaignData.data.attributes.campaign_pledge_sum + } + return data +} diff --git a/packages/next/app/lib/pm.ts b/packages/next/app/lib/pm.ts new file mode 100644 index 0000000..9eb3183 --- /dev/null +++ b/packages/next/app/lib/pm.ts @@ -0,0 +1,139 @@ +import matter from 'gray-matter'; + +const CACHE_TIME = 3600; +const GOAL_LABEL = 'Goal'; + +export interface IIssue { + id: number; + title: string; + comments: number; + updatedAt: string; + createdAt: string; + assignee: string | null; + name: string | null; + completedPercentage: number | null; + amountCents: number | null; + description: string | null; +} + +export interface IGoals { + complete: IIssue[]; + inProgress: IIssue[]; + planned: IIssue[]; + featuredFunded: IIssue; + featuredUnfunded: IIssue; +} + + +export interface IGiteaIssue { + id: number; + title: string; + body: string; + comments: number; + updated_at: string; + created_at: string; + assignee: string | null; +} + +const bigHairyAudaciousGoal: IIssue = { + id: 55234234, + title: 'BHAG', + comments: 0, + updatedAt: '2023-09-20T08:54:01.373Z', + createdAt: '2023-09-20T08:54:01.373Z', + assignee: null, + name: 'Big Hairy Audacious Goal', + description: 'World domination!!!!!1', + amountCents: 100000000, + completedPercentage: 0.04 +}; + +const defaultGoal: IIssue = { + id: 55234233, + title: 'e', + comments: 0, + updatedAt: '2023-09-20T08:54:01.373Z', + createdAt: '2023-09-20T08:54:01.373Z', + assignee: null, + name: 'Generic', + description: 'Getting started', + amountCents: 200, + completedPercentage: 1 +}; + +export function calcPercent(goalAmountCents: number, totalPledgeSumCents: number): number { + if (!goalAmountCents || totalPledgeSumCents <= 0) { + return 0; + } + const output = Math.min(100, Math.floor((totalPledgeSumCents / goalAmountCents) * 100)); + return output; +} + +export async function getGoals(pledgeSum: number): Promise { + try { + const openData = await fetchAndParseData('open', pledgeSum); + const closedData = await fetchAndParseData('closed', pledgeSum); + + + const inProgress = filterByAssignee(openData); + const planned = filterByAssignee(openData, true); + const funded = filterAndSortGoals(openData.concat(closedData), true); + const unfunded = filterAndSortGoals(openData.concat(closedData), false); + + console.log('the following are unfunded goals') + console.log(unfunded) + + return { + complete: closedData, + inProgress, + planned, + featuredFunded: funded[funded.length - 1] || defaultGoal, + featuredUnfunded: unfunded[0] || bigHairyAudaciousGoal + }; + } catch (error) { + console.error('Error fetching goals:', error); + return null; + } +} + +function filterByAssignee(issues: IIssue[], isPlanned: boolean = false): IIssue[] { + return issues.filter((issue) => (isPlanned ? issue.assignee === null : issue.assignee !== null)) +} + +async function fetchAndParseData(state: 'open' | 'closed', pledgeSum: number): Promise { + const response = await fetch(`https://gitea.futureporn.net/api/v1/repos/futureporn/pm/issues?state=${state}&labels=${GOAL_LABEL}`, { + next: { + revalidate: CACHE_TIME, + tags: ['goals'] + }, + }); + + if (!response.ok) return []; + + return response.json().then(issues => issues.map((g: IGiteaIssue) => parseGiteaGoal(g, pledgeSum))); +} + + + +function filterAndSortGoals(issues: IIssue[], isFunded: boolean): IIssue[] { + return issues + .filter((issue) => issue.amountCents) + .filter((issue) => (issue.completedPercentage === 100) === isFunded) + .sort((b, a) => b.amountCents! - a.amountCents!); +} + +function parseGiteaGoal(giteaIssue: IGiteaIssue, pledgeSum: number): IIssue { + const headMatter: any = matter(giteaIssue.body); + return { + id: giteaIssue.id, + title: giteaIssue.title, + comments: giteaIssue.comments, + updatedAt: giteaIssue.updated_at, + createdAt: giteaIssue.created_at, + assignee: giteaIssue.assignee, + name: headMatter.data.name || '', + description: headMatter.data.description || '', + amountCents: headMatter.data.amountCents || 0, + completedPercentage: calcPercent(headMatter.data.amountCents, pledgeSum) + }; +} diff --git a/packages/next/app/lib/retry.ts b/packages/next/app/lib/retry.ts new file mode 100644 index 0000000..68a4d7d --- /dev/null +++ b/packages/next/app/lib/retry.ts @@ -0,0 +1,13 @@ +export async function retry(fn: Function, maxRetries: number) { + let retries = 0; + while (retries < maxRetries) { + try { + return await fn(); + } catch (error) { + console.error(`Error during fetch attempt ${retries + 1}:`, error); + retries++; + } + } + console.error(`Max retries (${maxRetries}) reached. Giving up.`); + return null; +} \ No newline at end of file diff --git a/packages/next/app/lib/rss.ts b/packages/next/app/lib/rss.ts new file mode 100644 index 0000000..121d43b --- /dev/null +++ b/packages/next/app/lib/rss.ts @@ -0,0 +1,51 @@ +import { authorName, authorEmail, siteUrl, title, description, siteImage, favicon, authorLink } from './constants' +import { Feed } from "feed"; +import { getVods, getUrl, IVod } from '@/lib/vods' +import { ITagVodRelation } from '@/lib/tag-vod-relations'; + +export async function generateFeeds() { + const feedOptions = { + id: siteUrl, + title: title, + description: description, + link: siteUrl, + language: 'en', + image: siteImage, + favicon: favicon, + copyright: '', + generator: ' ', + feedLinks: { + json: `${siteUrl}/feed/feed.json`, + atom: `${siteUrl}/feed/feed.xml` + }, + author: { + name: authorName, + email: authorEmail, + link: authorLink + } + }; + + const feed = new Feed(feedOptions); + + const vods = await getVods() + + vods.data.map((vod: IVod) => { + feed.addItem({ + title: vod.attributes.title || vod.attributes.announceTitle, + description: vod.attributes.title, // @todo vod.attributes.spoiler or vod.attributes.note could go here + content: vod.attributes.tagVodRelations.data.map((tvr: ITagVodRelation) => tvr.attributes.tag.data.attributes.name).join(' '), + link: getUrl(vod, vod.attributes.vtuber.data.attributes.slug, vod.attributes.date2), + date: new Date(vod.attributes.date2), + image: vod.attributes.vtuber.data.attributes.image + }) + }) + + + return { + atom1: feed.atom1(), + rss2: feed.rss2(), + json1: feed.json1() + } +} + + diff --git a/packages/next/app/lib/shareRef.ts b/packages/next/app/lib/shareRef.ts new file mode 100644 index 0000000..5a6dddc --- /dev/null +++ b/packages/next/app/lib/shareRef.ts @@ -0,0 +1,16 @@ +import type { MutableRefObject, RefCallback } from 'react'; + +type RefType = MutableRefObject | RefCallback | null; + +export const shareRef = (refA: RefType, refB: RefType): RefCallback => instance => { + if (typeof refA === 'function') { + refA(instance); + } else if (refA && 'current' in refA) { + (refA as MutableRefObject).current = instance as T; // Use type assertion to tell TypeScript the type + } + if (typeof refB === 'function') { + refB(instance); + } else if (refB && 'current' in refB) { + (refB as MutableRefObject).current = instance as T; // Use type assertion to tell TypeScript the type + } +}; diff --git a/packages/next/app/lib/streams.ts b/packages/next/app/lib/streams.ts new file mode 100644 index 0000000..1a45abf --- /dev/null +++ b/packages/next/app/lib/streams.ts @@ -0,0 +1,369 @@ + +import { strapiUrl, siteUrl } from './constants'; +import { getSafeDate } from './dates'; +import { IVodsResponse } from './vods'; +import { IVtuber, IVtuberResponse } from './vtubers'; +import { ITweetResponse } from './tweets'; +import { IMeta } from './types'; +import qs from 'qs'; + + +export interface IStream { + id: number; + attributes: { + date: string; + archiveStatus: 'good' | 'issue' | 'missing'; + vods: IVodsResponse; + cuid: string; + vtuber: IVtuberResponse; + tweet: ITweetResponse; + isChaturbateStream: boolean; + isFanslyStream: boolean; + } +} + +export interface IStreamResponse { + data: IStream; + meta: IMeta; +} + +export interface IStreamsResponse { + data: IStream[]; + meta: IMeta; +} + + +const fetchStreamsOptions = { + next: { + tags: ['streams'] + } +} + + +export async function getStreamByCuid(cuid: string): Promise { + const query = qs.stringify({ + filters: { + cuid: { + $eq: cuid + } + }, + pagination: { + limit: 1 + }, + populate: { + vtuber: { + fields: ['slug', 'displayName'] + }, + tweet: { + fields: ['isChaturbateInvite', 'isFanslyInvite', 'url'] + }, + vods: { + fields: ['note', 'cuid', 'publishedAt'], + populate: { + tagVodRelations: { + fields: ['id'] + }, + timestamps: '*' + } + } + } + }); + const res = await fetch(`${strapiUrl}/api/streams?${query}`); + const json = await res.json(); + return json.data[0]; +} + +export function getUrl(stream: IStream, slug: string, date: string): string { + return `${siteUrl}/vt/${slug}/stream/${getSafeDate(date)}` +} + + +export function getPaginatedUrl(): (slug: string, pageNumber: number) => string { + return (slug: string, pageNumber: number) => { + return `${siteUrl}/vt/${slug}/streams/${pageNumber}` + } +} + + + +export function getLocalizedDate(stream: IStream): string { + return new Date(stream.attributes.date).toLocaleDateString() +} + + + + +export async function getStreamsForYear(year: number): Promise { + const startOfYear = new Date(year, 0, 0); + const endOfYear = new Date(year, 11, 31); + + const pageSize = 100; // Number of records per page + let currentPage = 0; + let allStreams: IStream[] = []; + + while (true) { + const query = qs.stringify({ + filters: { + date: { + $gte: startOfYear, + $lte: endOfYear, + }, + }, + populate: { + vtuber: { + fields: ['displayName'] + } + }, + pagination: { + page: currentPage, + pageSize: pageSize, + } + }); + + const res = await fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions); + + if (!res.ok) { + // Handle error if needed + console.error('here is the res.body') + + console.error((await res.text())); + throw new Error(`Error fetching streams: ${res.status}`); + } + + const json = await res.json(); + const streams = json as IStreamsResponse; + + if (streams.data.length === 0) { + // No more records, break the loop + break; + } + + allStreams = [...allStreams, ...streams.data]; + currentPage += pageSize; + } + + return allStreams; + } + +export async function getStream(id: number): Promise { + const query = qs.stringify({ + filters: { + id: { + $eq: id + } + } + }); + const res = await fetch(`${strapiUrl}/api/vods?${query}`, fetchStreamsOptions); + const json = await res.json(); + return json.data; +} + + + + +export async function getAllStreams(archiveStatuses = ['missing', 'issue', 'good']): Promise { + const pageSize = 100; // Adjust this value as needed + const sortDesc = true; // Adjust the sorting direction as needed + + const allStreams: IStream[] = []; + let currentPage = 1; + + while (true) { + const query = qs.stringify({ + populate: { + vtuber: { + fields: ['slug', 'displayName', 'image', 'imageBlur', 'themeColor'], + }, + muxAsset: { + fields: ['playbackId', 'assetId'], + }, + thumbnail: { + fields: ['cdnUrl', 'url'], + }, + tagstreamRelations: { + fields: ['tag'], + populate: ['tag'], + }, + videoSrcB2: { + fields: ['url', 'key', 'uploadId', 'cdnUrl'], + }, + tweet: { + fields: ['isChaturbateInvite', 'isFanslyInvite'] + } + }, + filters: { + archiveStatus: { + '$in': archiveStatuses + } + }, + sort: { + date: sortDesc ? 'desc' : 'asc', + }, + pagination: { + pageSize, + page: currentPage, + }, + }); + const response = await fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions); + const responseData = await response.json(); + + if (!responseData.data || responseData.data.length === 0) { + // No more data to fetch + break; + } + + allStreams.push(...responseData.data); + currentPage++; + } + + return allStreams; +} + +export async function getStreamForVtuber(vtuberId: number, safeDate: string): Promise { + const query = qs.stringify({ + populate: { + vods: { + fields: [ + 'id', + 'date' + ] + }, + tweet: { + fields: [ + 'id' + ] + } + } + }); + + const response = await fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions); + + if (response.status !== 200) throw new Error('network fetch error while attempting to getStreamForVtuber'); + + const responseData = await response.json(); + return responseData; +} + +export async function getAllStreamsForVtuber(vtuberId: number, archiveStatuses = ['missing', 'issue', 'good']): Promise { + const maxRetries = 3; + + let retries = 0; + let allStreams: IStream[] = []; + let currentPage = 1; + + while (retries < maxRetries) { + try { + const query = qs.stringify({ + populate: '*', + filters: { + archiveStatus: { + '$in': archiveStatuses + }, + vtuber: { + id: { + $eq: vtuberId + } + } + }, + sort: { + date: 'desc', + }, + pagination: { + pageSize: 100, + page: currentPage, + }, + }); + + console.log(`strapiUrl=${strapiUrl}`) + const response = await fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions); + + if (response.status !== 200) { + // If the response status is not 200 (OK), consider it a network failure + const bod = await response.text(); + console.log(response.status); + console.log(bod); + retries++; + continue; + } + + const responseData = await response.json(); + + if (!responseData.data || responseData.data.length === 0) { + // No more data to fetch + break; + } + + allStreams.push(...responseData.data); + currentPage++; + } catch (error) { + // Network failure or other error occurred + retries++; + } + } + + if (retries === maxRetries) { + throw new Error(`Failed to fetch streams after ${maxRetries} retries.`); + } + + return allStreams; +} + +export async function getStreamsForVtuber(vtuberId: number, page: number = 1, pageSize: number = 25, sortDesc = true): Promise { + const query = qs.stringify( + { + populate: { + vtuber: { + fields: [ + 'id', + ] + } + }, + filters: { + vtuber: { + id: { + $eq: vtuberId + } + } + }, + pagination: { + page: page, + pageSize: pageSize + }, + sort: { + date: (sortDesc) ? 'desc' : 'asc' + } + } + ) + return fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions) + .then((res) => res.json()) +} + + +// /** +// * This returns stale data, because futureporn-historian is broken. +// * @todo get live data from historian +// * @see https://gitea.futureporn.net/futureporn/futureporn-historian/issues/1 +// */ +// export async function getProgress(vtuberSlug: string): Promise<{ complete: number; total: number }> { +// const query = qs.stringify({ +// filters: { +// vtuber: { +// slug: { +// $eq: vtuberSlug +// } +// } +// } +// }) +// const data = await fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions) +// .then((res) => res.json()) +// .then((g) => { +// return g +// }) + +// const total = data.meta.pagination.total + +// return { +// complete: total, +// total: total +// } +// } \ No newline at end of file diff --git a/packages/next/app/lib/tag-vod-relations.ts b/packages/next/app/lib/tag-vod-relations.ts new file mode 100644 index 0000000..71d6e52 --- /dev/null +++ b/packages/next/app/lib/tag-vod-relations.ts @@ -0,0 +1,191 @@ +/** + * Tag Vod Relations are an old name for what I'm now calling, "VodTag" + * + * VodTags are Tags related to Vods + * + */ + + +import qs from 'qs'; +import { strapiUrl } from './constants' +import { ITagResponse, IToyTagResponse } from './tags'; +import { IVod, IVodResponse } from './vods'; +import { IAuthData } from '@/components/auth'; +import { IMeta } from './types'; + +export interface ITagVodRelation { + id: number; + attributes: { + tag: ITagResponse | IToyTagResponse; + vod: IVodResponse; + creatorId: number; + createdAt: string; + } +} + + +export interface ITagVodRelationsResponse { + data: ITagVodRelation[]; + meta: IMeta; +} + + + + +export async function deleteTvr(authData: IAuthData, tagId: number) { + return fetch(`${strapiUrl}/api/tag-vod-relations/deleteMine/${tagId}`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${authData.accessToken}`, + 'Content-Type': 'application/json' + } + }) + .then((res) => { + if (!res.ok) throw new Error(res.statusText); + else return res.json(); + }) + .catch((e) => { + console.error(e); + // setError('root.serverError', { message: e.message }) + }) +} + +export async function readTagVodRelation(accessToken: string, tagId: number, vodId: number): Promise { + if (!tagId) throw new Error('readTagVodRelation requires tagId as second param'); + if (!vodId) throw new Error('readTagVodRelation requires vodId as second param'); + const findQuery = qs.stringify({ + filters: { + $and: [ + { + tag: tagId + }, { + vod: vodId + } + ] + } + }); + const res = await fetch(`${strapiUrl}/api/tag-vod-relations?${findQuery}`); + const json = await res.json(); + return json.data[0]; +} + +export async function createTagVodRelation(accessToken: string, tagId: number, vodId: number): Promise { + if (!accessToken) throw new Error('Must be logged in'); + if (!tagId) throw new Error('tagId is required.'); + if (!vodId) throw new Error('vodId is required.'); + const payload = { + tag: tagId, + vod: vodId + } + const res = await fetch(`${strapiUrl}/api/tag-vod-relations`, { + method: 'POST', + body: JSON.stringify({ data: payload }), + headers: { + authorization: `Bearer ${accessToken}`, + 'content-type': 'application/json' + } + }) + const json = await res.json(); + console.log(json) + return json.data; +} + +export async function readOrCreateTagVodRelation (accessToken: string, tagId: number, vodId: number): Promise { + console.log(`Checking if the tagVodRelation with tagId=${tagId}, vodId=${vodId} already exists`); + const existingTagVodRelation = await readTagVodRelation(accessToken, tagId, vodId); + if (!!existingTagVodRelation) { + console.log(`there is an existing TVR so we return it`); + console.log(existingTagVodRelation); + return existingTagVodRelation + } + const newTagVodRelation = await createTagVodRelation(accessToken, tagId, vodId); + return newTagVodRelation; +} + +// export async function createTagAndTvr(setError: Function, authData: IAuthData, tagName: string, vodId: number) { +// if (!authData) throw new Error('Must be logged in'); +// if (!tagName || tagName === '') throw new Error('tagName cannot be empty'); +// const data = { +// tagName: tagName, +// vodId: vodId +// }; +// try { +// const res = await fetch(`${strapiUrl}/api/tag-vod-relations/tag`, { +// method: 'POST', +// body: JSON.stringify({ data }), +// headers: { +// 'Content-Type': 'application/json', +// 'Authorization': `Bearer ${authData.accessToken}` +// }, +// }); +// const json = await res.json(); +// return json.data; +// } catch (e) { +// setError('global', { type: 'idk', message: e }) +// } +// } + + +export async function getTagVodRelationsForVtuber(vtuberId: number, page: number = 1, pageSize: number = 25): Promise { + // get the tag-vod-relations where the vtuber is the vtuber we are interested in. + const query = qs.stringify( + { + populate: { + tag: { + fields: ['id', 'name'], + populate: { + toy: { + fields: ['linkTag', 'make', 'model', 'image2'], + populate: { + linkTag: { + fields: ['name'] + } + } + } + } + }, + vod: { + populate: { + vtuber: { + fields: ['slug'] + } + } + } + }, + filters: { + vod: { + vtuber: { + id: { + $eq: vtuberId + } + } + }, + tag: { + toy: { + linkTag: { + name: { + $notNull: true + } + } + } + } + }, + pagination: { + page: page, + pageSize: pageSize + }, + sort: { + id: 'desc' + } + } + ) + // we need to return an IToys object + // to get an IToys object, we have to get a list of toys from tvrs. + + + const res = await fetch(`${strapiUrl}/api/tag-vod-relations?${query}`); + if (!res.ok) return null; + const tvrs = await res.json() + return tvrs; +} + diff --git a/packages/next/app/lib/tags.ts b/packages/next/app/lib/tags.ts new file mode 100644 index 0000000..ee5385d --- /dev/null +++ b/packages/next/app/lib/tags.ts @@ -0,0 +1,139 @@ +import { strapiUrl } from './constants' +import { fetchPaginatedData } from './fetchers'; +import { IVod } from './vods'; +import slugify from 'slugify'; +import { IToy } from './toys'; +import { IAuthData } from '@/components/auth'; +import qs from 'qs'; +import { IMeta } from './types'; + + +export interface ITag { + id: number; + attributes: { + name: string; + count: number; + } +} + +export interface ITagsResponse { + data: ITag[]; + meta: IMeta; +} + +export interface ITagResponse { + data: ITag; + meta: IMeta; +} + +export interface IToyTagResponse { + data: IToyTag; + meta: IMeta; +} + + +export interface IToyTag extends ITag { + toy: IToy; +} + + + +export function getTagHref(name: string): string { + return `/tags/${slugify(name)}` +} + + +export async function createTag(accessToken: string, tagName: string): Promise { + const payload = { + name: slugify(tagName) + }; + const res = await fetch(`${strapiUrl}/api/tags`, { + method: 'POST', + headers: { + 'authorization': `Bearer ${accessToken}`, + 'accept': 'application/json', + 'content-type': 'application/json' + }, + body: JSON.stringify({ data: payload }) + }); + const json = await res.json(); + console.log(json); + if (!!json?.error) throw new Error(json.error.message); + if (!json?.data) throw new Error('created tag was missing data'); + return json.data as ITag; +} + +export async function readTag(accessToken: string, tagName: string): Promise { + + const findQuery = qs.stringify({ + filters: { + name: { + $eq: tagName + } + } + }); + const findResponse = await fetch(`${strapiUrl}/api/tags?${findQuery}`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + }); + + const json = await findResponse.json(); + return json.data[0]; +} + +export async function readOrCreateTag(accessToken: string, tagName: string): Promise { + console.log(`Checking if the tagName=${tagName} already exists`); + + const existingTag = await readTag(accessToken, tagName); + if (!!existingTag) { + console.log('there is an existing tag so we return it'); + console.log(existingTag); + return existingTag; + } + + const newTag = await createTag(accessToken, tagName); + return newTag; + + +} + +export async function getTags(): Promise { + const tagVodRelations = await fetchPaginatedData('/api/tag-vod-relations', 100, { 'populate[0]': 'tag', 'populate[1]': 'vod' }); + + // Create a Map to store tag data, including counts and IDs + const tagDataMap = new Map(); + + // Populate the tag data map with counts and IDs + tagVodRelations.forEach(tvr => { + const tagName = tvr.attributes.tag.data.attributes.name; + const tagId = tvr.attributes.tag.data.id; + + if (!tagDataMap.has(tagName)) { + tagDataMap.set(tagName, { id: tagId, count: 1 }); + } else { + const existingData = tagDataMap.get(tagName); + if (existingData) { + tagDataMap.set(tagName, { id: existingData.id, count: existingData.count + 1 }); + } + } + }); + + // Create an array of Tag objects with id, name, and count + const tags = Array.from(tagDataMap.keys()).map(tagName => { + const tagData = tagDataMap.get(tagName); + return { + id: tagData ? tagData.id : -1, + attributes: { + name: tagName, + count: tagData ? tagData.count : 0, + } + }; + }); + + + return tags; +} diff --git a/packages/next/app/lib/timestamps.ts b/packages/next/app/lib/timestamps.ts new file mode 100644 index 0000000..e80cf54 --- /dev/null +++ b/packages/next/app/lib/timestamps.ts @@ -0,0 +1,127 @@ + + +import qs from 'qs'; +import { strapiUrl } from './constants' +import { IAuthData } from '@/components/auth'; +import { ITagsResponse, ITag, ITagResponse } from './tags'; +import { IMeta } from './types'; + +export interface ITimestamp { + id: number; + attributes: { + time: number; + tagName: string; + tnShort: string; + tagId: number; + vodId: number; + tag: ITagResponse; + createdAt: string; + creatorId: number; + } +} + + + +export interface ITimestampResponse { + data: ITimestamp; + meta: IMeta; +} + +export interface ITimestampsResponse { + data: ITimestamp[]; + meta: IMeta; +} + +function truncateString(str: string, maxLength: number) { + if (str.length <= maxLength) { + return str; + } + return str.substring(0, maxLength - 1) + '…'; +} + +export function deleteTimestamp(authData: IAuthData, tsId: number) { + return fetch(`${strapiUrl}/api/timestamps/deleteMine/${tsId}`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${authData.accessToken}`, + 'Content-Type': 'application/json' + } + }) + .then((res) => { + if (!res.ok) throw new Error(res.statusText); + else return res.json(); + }) + .catch((e) => { + console.error(e); + // setError('root.serverError', { message: e.message }) + }) +} + +export async function createTimestamp( + authData: IAuthData, + tagId: number, + vodId: number, + time: number +): Promise { + if (!authData?.user?.id || !authData?.accessToken) throw new Error('User must be logged in to create timestamps'); + const query = qs.stringify({ + populate: '*' + }); + const response = await fetch(`${strapiUrl}/api/timestamps?${query}`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${authData.accessToken}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + data: { + time: Math.floor(time), + tag: tagId, + vod: vodId, + creatorId: authData.user.id + } + }) + }); + + const json = await response.json(); + + if (!response.ok) { + throw new Error(json?.error?.message || response.statusText); + } + + return json.data; +} + + + +export async function getTimestampsForVod(vodId: number, page: number = 1, pageSize: number = 25): Promise { + const query = qs.stringify({ + filters: { + vod: { + id: { + $eq: vodId, + }, + }, + }, + populate: '*', + sort: 'time:asc', + pagination: { + page: page, + pageSize: pageSize, + }, + }); + + const response = await fetch(`${strapiUrl}/api/timestamps?${query}`); + const data = await response.json() as ITimestampsResponse; + + const timestamps: ITimestamp[] = data.data || []; + + // If there are more pages, recursively fetch them and concatenate the results + if (data.meta.pagination && (data.meta.pagination.page < data.meta.pagination.pageCount)) { + const nextPage = (data.meta.pagination.page + 1); + const nextPageTimestamps = await getTimestampsForVod(vodId, nextPage, pageSize); + timestamps.push(...nextPageTimestamps); + } + + return timestamps; +} \ No newline at end of file diff --git a/packages/next/app/lib/toys.ts b/packages/next/app/lib/toys.ts new file mode 100644 index 0000000..dee0e7b --- /dev/null +++ b/packages/next/app/lib/toys.ts @@ -0,0 +1,42 @@ + +import { ITag, ITagResponse, ITagsResponse } from '@/lib/tags' +import { IMeta } from './types'; + + +export interface IToysResponse { + data: IToy[]; + meta: IMeta; +} + +export interface IToy { + id: number; + attributes: { + tags: ITagsResponse; + linkTag: ITagResponse; + make: string; + model: string; + aspectRatio: string; + image2: string; + } +} + + +interface IToysListProps { + toys: IToysResponse; + page: number; + pageSize: number; +} + + +/** This endpoint doesn't exist at the moment, but definitely could in the future */ +// export function getUrl(toy: IToy): string { +// return `${siteUrl}/toy/${toy.name}` +// } + +// export function getToysForVtuber(vtuberId: number, page: number = 1, pageSize: number = 25): Promise { +// const tvrs = await getTagVodRelationsForVtuber(vtuberId, page, pageNumber); +// return { +// data: tvrs.data. +// pagination: tvrs.pagination +// } +// } diff --git a/packages/next/app/lib/tweets.ts b/packages/next/app/lib/tweets.ts new file mode 100644 index 0000000..caf9607 --- /dev/null +++ b/packages/next/app/lib/tweets.ts @@ -0,0 +1,28 @@ +import { IVtuberResponse } from "./vtubers"; +import { IMeta } from "./types"; + +export interface ITweet { + id: number; + attributes: { + date: string; + date2: string; + isChaturbateInvite: boolean; + isFanslyInvite: boolean; + cuid: string; + json: string; + id_str: string; + url: string; + vtuber: IVtuberResponse; + } +} + +export interface ITweetResponse { + data: ITweet; + meta: IMeta; +} + +export interface ITweetsResponse { + data: ITweet[]; + meta: IMeta; +} + diff --git a/packages/next/app/lib/types.ts b/packages/next/app/lib/types.ts new file mode 100644 index 0000000..8a2cc29 --- /dev/null +++ b/packages/next/app/lib/types.ts @@ -0,0 +1,28 @@ + + + + + +export interface IMuxAsset { + id: number; + attributes: { + playbackId: string; + assetId: string; + } +} + +export interface IPagination { + page: number; + pageSize: number; + pageCount: number; + total: number; +} + +export interface IMuxAssetResponse { + data: IMuxAsset; + meta: IMeta; +} + +export interface IMeta { + pagination: IPagination; +} diff --git a/packages/next/app/lib/useForwardRef.ts b/packages/next/app/lib/useForwardRef.ts new file mode 100644 index 0000000..7220d13 --- /dev/null +++ b/packages/next/app/lib/useForwardRef.ts @@ -0,0 +1,27 @@ +/** + * greetz https://github.com/facebook/react/issues/24722#issue-1270749463 + */ + +import React, { useEffect, useRef, type ForwardedRef } from 'react'; + +const useForwardRef = ( + ref: ForwardedRef, + initialValue: any = null +) => { + const targetRef = useRef(initialValue); + + useEffect(() => { + if (!ref) return; + + if (typeof ref === 'function') { + ref(targetRef.current); + } else { + ref.current = targetRef.current; + } + }, [ref]); + + return targetRef; +}; + + +export default useForwardRef \ No newline at end of file diff --git a/packages/next/app/lib/users.ts b/packages/next/app/lib/users.ts new file mode 100644 index 0000000..c3fa3ed --- /dev/null +++ b/packages/next/app/lib/users.ts @@ -0,0 +1,16 @@ +import { IMeta } from "./types"; + + +export interface IUser { + id: number; + attributes: { + username: string; + vanityLink?: string; + image: string; + } +} + +export interface IUserResponse { + data: IUser; + meta: IMeta; +} \ No newline at end of file diff --git a/packages/next/app/lib/vods.ts b/packages/next/app/lib/vods.ts new file mode 100644 index 0000000..b4ad2ac --- /dev/null +++ b/packages/next/app/lib/vods.ts @@ -0,0 +1,502 @@ + +import { strapiUrl, siteUrl } from './constants'; +import { getDateFromSafeDate, getSafeDate } from './dates'; +import { IVtuber, IVtuberResponse } from './vtubers'; +import { IStream, IStreamResponse } from './streams'; +import qs from 'qs'; +import { ITagVodRelationsResponse } from './tag-vod-relations'; +import { ITimestampsResponse } from './timestamps'; +import { IMeta, IMuxAsset, IMuxAssetResponse } from './types'; +import { IB2File, IB2FileResponse } from '@/lib/b2File'; +import fetchAPI from './fetch-api'; +import { IUserResponse } from './users'; + +/** + * Dec 2023 CUIDs were introduced. + * Going forward, use CUIDs where possible. + * safeDates are retained for backwards compatibility. + * + * @see https://www.w3.org/Provider/Style/URI + */ +export interface IVodPageProps { + params: { + safeDateOrCuid: string; + slug: string; + }; +} + +export interface IVodsResponse { + data: IVod[]; + meta: IMeta; +} + +export interface IVodResponse { + data: IVod; + meta: IMeta; +} + +export interface IVod { + id: number; + attributes: { + stream: IStreamResponse; + publishedAt?: string; + cuid: string; + title?: string; + duration?: number; + date: string; + date2: string; + muxAsset: IMuxAssetResponse; + thumbnail?: IB2FileResponse; + vtuber: IVtuberResponse; + tagVodRelations: ITagVodRelationsResponse; + timestamps: ITimestampsResponse; + video240Hash: string; + videoSrcHash: string; + videoSrcB2: IB2FileResponse | null; + announceTitle: string; + announceUrl: string; + uploader: IUserResponse; + note: string; + } +} + +const fetchVodsOptions = { + next: { + tags: ['vods'] + } +} + + +export async function getVodFromSafeDateOrCuid(safeDateOrCuid: string): Promise { + let vod: IVod|null; + let date: Date; + if (!safeDateOrCuid) { + console.log(`safeDateOrCuid was missing`); + return null; + } else if (/^[0-9a-z]{10}$/.test(safeDateOrCuid)) { + console.log('this is a CUID!'); + vod = await getVodByCuid(safeDateOrCuid); + if (!vod) return null; + } else { + console.log('This is a safeDate!'); + date = await getDateFromSafeDate(safeDateOrCuid); + if (!date) { + console.log('there is no date') + return null; + } else { + console.log(`date=${date}`) + } + vod = await getVodForDate(date); + } + return vod; +} + +export function getUrl(vod: IVod, slug: string, date: string): string { + return `${siteUrl}/vt/${slug}/vod/${getSafeDate(date)}` +} + + +export function getPaginatedUrl(): (slug: string, pageNumber: number) => string { + return (slug: string, pageNumber: number) => { + return `${siteUrl}/vt/${slug}/vods/${pageNumber}` + } +} + + +/** @deprecated old format for futureporn.net/api/v1.json, which is deprecated. Please use getUrl() instead */ +export function getDeprecatedUrl(vod: IVod): string { + return `${siteUrl}/vods/${getSafeDate(vod.attributes.date2)}` +} + +export async function getNextVod(vod: IVod): Promise { + const query = qs.stringify({ + filters: { + date2: { + $gt: vod.attributes.date2 + }, + vtuber: { + slug: { + $eq: vod.attributes.vtuber.data.attributes.slug + } + }, + publishedAt: { + $notNull: true + } + }, + sort: { + date2: 'asc' + }, + fields: ['date2', 'title', 'announceTitle'], + populate: { + vtuber: { + fields: ['slug'] + } + } + }) + const res = await fetch(`${strapiUrl}/api/vods?${query}`, fetchVodsOptions); + if (!res.ok) throw new Error('could not fetch next vod'); + const json = await res.json(); + const nextVod = json.data[0]; + if (!nextVod) return null; + return nextVod + +} + +export function getLocalizedDate(vod: IVod): string { + return new Date(vod.attributes.date2).toLocaleDateString() +} + +export async function getPreviousVod(vod: IVod): Promise { + const res = await fetchAPI( + '/vods', + { + filters: { + date2: { + $lt: vod.attributes.date2 + }, + vtuber: { + slug: { + $eq: vod.attributes.vtuber.data.attributes.slug + } + } + }, + sort: { + date2: 'desc' + }, + fields: ['date2', 'title', 'announceTitle'], + populate: { + vtuber: { + fields: ['slug'] + } + }, + pagination: { + limit: 1 + } + }, + fetchVodsOptions + ) + return res.data[0]; +} + +export async function getVodByCuid(cuid: string): Promise { + const query = qs.stringify( + { + filters: { + cuid: { + $eq: cuid + } + }, + populate: { + vtuber: { + fields: ['slug', 'displayName', 'image', 'imageBlur', 'themeColor'] + }, + muxAsset: { + fields: ['playbackId', 'assetId'] + }, + thumbnail: { + fields: ['cdnUrl', 'url'] + }, + tagVodRelations: { + fields: ['tag', 'createdAt', 'creatorId'], + populate: ['tag'] + }, + videoSrcB2: { + fields: ['url', 'key', 'uploadId', 'cdnUrl'] + }, + stream: { + fields: ['archiveStatus', 'date', 'tweet', 'cuid'] + } + } + }) + + try { + const res = await fetch(`${strapiUrl}/api/vods?${query}`, { cache: 'no-store', next: { tags: ['vods'] } }) + if (!res.ok) { + throw new Error('failed to fetch vodForDate') + } + const json = await res.json() + const vod = json.data[0] + if (!vod) return null; + return vod; + } catch (e) { + if (e instanceof Error) { + console.error(e) + } + return null; + } +} + +export async function getVodForDate(date: Date): Promise { + // if (!date) return null; + // console.log(date) + // console.log(`getting vod for ${date.toISOString()}`) + try { + const iso8601DateString = date.toISOString().split('T')[0]; + const query = qs.stringify( + { + filters: { + date2: { + $eq: date.toISOString() + } + }, + populate: { + vtuber: { + fields: ['slug', 'displayName', 'image', 'imageBlur', 'themeColor'] + }, + muxAsset: { + fields: ['playbackId', 'assetId'] + }, + thumbnail: { + fields: ['cdnUrl', 'url'] + }, + tagVodRelations: { + fields: ['tag', 'createdAt', 'creatorId'], + populate: ['tag'] + }, + videoSrcB2: { + fields: ['url', 'key', 'uploadId', 'cdnUrl'] + }, + stream: { + fields: ['archiveStatus', 'date', 'tweet', 'cuid'] + } + } + } + ) + const res = await fetch(`${strapiUrl}/api/vods?${query}`, { cache: 'no-store', next: { tags: ['vods'] } }) + if (!res.ok) { + throw new Error('failed to fetch vodForDate') + } + const json = await res.json() + const vod = json.data[0] + if (!vod) return null; + return vod; + } catch (e) { + + return null; + } +} + +export async function getVod(id: number): Promise { + const query = qs.stringify( + { + filters: { + id: { + $eq: id + } + } + } + ) + const res = await fetch(`${strapiUrl}/api/vods?${query}`, fetchVodsOptions); + if (!res.ok) return null; + const data = await res.json(); + return data; +} + +export async function getVods(page: number = 1, pageSize: number = 25, sortDesc = true): Promise { + const query = qs.stringify( + { + populate: { + vtuber: { + fields: ['slug', 'displayName', 'image', 'imageBlur'] + }, + muxAsset: { + fields: ['playbackId', 'assetId'] + }, + thumbnail: { + fields: ['cdnUrl', 'url'] + }, + tagVodRelations: { + fields: ['tag'], + populate: ['tag'] + }, + videoSrcB2: { + fields: ['url', 'key', 'uploadId', 'cdnUrl'] + } + }, + sort: { + date: (sortDesc) ? 'desc' : 'asc' + }, + pagination: { + pageSize: pageSize, + page: page + } + } + ) + + const res = await fetch(`${strapiUrl}/api/vods?${query}`, fetchVodsOptions); + if (!res.ok) { + throw new Error('Failed to fetch vods'); + } + const json = await res.json() + return json; +} + + + +export async function getAllVods(): Promise { + const pageSize = 100; // Adjust this value as needed + const sortDesc = true; // Adjust the sorting direction as needed + + const allVods: IVod[] = []; + let currentPage = 1; + + while (true) { + const query = qs.stringify({ + populate: { + vtuber: { + fields: ['slug', 'displayName', 'image', 'imageBlur'], + }, + muxAsset: { + fields: ['playbackId', 'assetId'], + }, + thumbnail: { + fields: ['cdnUrl', 'url'], + }, + tagVodRelations: { + fields: ['tag'], + populate: ['tag'], + }, + videoSrcB2: { + fields: ['url', 'key', 'uploadId', 'cdnUrl'], + }, + }, + sort: { + date: sortDesc ? 'desc' : 'asc', + }, + pagination: { + pageSize, + page: currentPage, + }, + }); + + try { + const response = await fetch(`${strapiUrl}/api/vods?${query}`, fetchVodsOptions); + + if (!response.ok) { + // Handle non-successful response (e.g., HTTP error) + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const responseData = await response.json(); + + if (!responseData.data || responseData.data.length === 0) { + // No more data to fetch + break; + } + + allVods.push(...responseData.data); + currentPage++; + } catch (error) { + // Handle fetch error + if (error instanceof Error) { + console.error('Error fetching data:', error.message); + } + return null; + } + } + + return allVods; +} + + + +export async function getVodsForVtuber(vtuberId: number, page: number = 1, pageSize: number = 25, sortDesc = true): Promise { + const query = qs.stringify( + { + populate: { + thumbnail: { + fields: ['cdnUrl', 'url'] + }, + vtuber: { + fields: [ + 'id', + 'slug', + 'displayName', + 'image', + 'imageBlur' + ] + }, + videoSrcB2: { + fields: ['url', 'key', 'uploadId', 'cdnUrl'] + } + }, + filters: { + vtuber: { + id: { + $eq: vtuberId + } + } + }, + pagination: { + page: page, + pageSize: pageSize + }, + sort: { + date: (sortDesc) ? 'desc' : 'asc' + } + } + ) + const res = await fetch(`${strapiUrl}/api/vods?${query}`, fetchVodsOptions) + if (!res.ok) return null; + const data = await res.json() as IVodsResponse; + return data; + +} + + +export async function getVodsForTag(tag: string): Promise { + const query = qs.stringify( + { + populate: { + vtuber: { + fields: ['slug', 'displayName', 'image', 'imageBlur'] + }, + videoSrcB2: { + fields: ['url', 'key', 'uploadId', 'cdnUrl'] + } + }, + filters: { + tagVodRelations: { + tag: { + name: { + $eq: tag + } + } + } + } + } + ) + const res = await fetch(`${strapiUrl}/api/vods?${query}`, fetchVodsOptions) + if (!res.ok) return null; + const vods = await res.json() + return vods; +} + +/** + * This returns stale data, because futureporn-historian is broken. + * @todo get live data from historian + * @see https://git.futureporn.net/futureporn/futureporn-historian/issues/1 + */ +export async function getProgress(vtuberSlug: string): Promise<{ complete: number; total: number }> { + const query = qs.stringify({ + filters: { + vtuber: { + slug: { + $eq: vtuberSlug + } + } + } + }) + const data = await fetch(`${strapiUrl}/api/vods?${query}`, fetchVodsOptions) + .then((res) => res.json()) + .then((g) => { + return g + }) + + const total = data.meta.pagination.total + + return { + complete: total, + total: total + } +} \ No newline at end of file diff --git a/packages/next/app/lib/vtubers.ts b/packages/next/app/lib/vtubers.ts new file mode 100644 index 0000000..d671ee3 --- /dev/null +++ b/packages/next/app/lib/vtubers.ts @@ -0,0 +1,171 @@ + + +import { IVod } from './vods' +import { strapiUrl, siteUrl } from './constants'; +import { getSafeDate } from './dates'; +import qs from 'qs'; +import { resourceLimits } from 'worker_threads'; +import { IMeta } from './types'; + + +const fetchVtubersOptions = { + next: { + tags: ['vtubers'] + } +} + + +export interface IVtuber { + id: number; + attributes: { + slug: string; + displayName: string; + chaturbate?: string; + twitter?: string; + patreon?: string; + twitch?: string; + tiktok?: string; + onlyfans?: string; + youtube?: string; + linktree?: string; + carrd?: string; + fansly?: string; + pornhub?: string; + discord?: string; + reddit?: string; + throne?: string; + instagram?: string; + facebook?: string; + merch?: string; + vods: IVod[]; + description1: string; + description2?: string; + image: string; + imageBlur?: string; + themeColor: string; + } +} + +export interface IVtuberResponse { + data: IVtuber; + meta: IMeta; +} + +export interface IVtubersResponse { + data: IVtuber[]; + meta: IMeta; +} + + +export function getUrl(slug: string): string { + return `${siteUrl}/vt/${slug}` +} + + + + +export async function getVtuberBySlug(slug: string): Promise { + const query = qs.stringify( + { + filters: { + slug: { + $eq: slug + } + } + } + ) + + const res = await fetch(`${strapiUrl}/api/vtubers?${query}`); + if (!res.ok) { + console.error(`error inside getVtuberBySlug-- ${res.statusText}`); + return null; + } + const vtuber = await res.json(); + return vtuber.data[0]; +} + +export async function getVtuberById(id: number): Promise { + const res = await fetch(`${strapiUrl}/api/vtubers?filters[id][$eq]=${id}`); + if (!res.ok) { + console.error(`error inside getVtuberById-- ${res.statusText}`); + return null; + } + const vtuber = await res.json(); + return vtuber +} + +export async function getVtubers(): Promise { + const res = await fetch(`${strapiUrl}/api/vtubers`); + if (!res.ok) { + console.error(`error inside getVtubers-- ${res.statusText}`); + return null; + } + const vtubers = await res.json(); + return vtubers; + +} + +export async function getAllVtubers(): Promise { + const pageSize = 100; + + const allVtubers: IVtuber[] = []; + let currentPage = 1; + + while (true) { + const query = qs.stringify({ + // populate: { + // vtuber: { + // fields: ['slug', 'displayName', 'image', 'imageBlur'], + // }, + // muxAsset: { + // fields: ['playbackId', 'assetId'], + // }, + // thumbnail: { + // fields: ['cdnUrl', 'url'], + // }, + // tagVodRelations: { + // fields: ['tag'], + // populate: ['tag'], + // }, + // videoSrcB2: { + // fields: ['url', 'key', 'uploadId', 'cdnUrl'], + // }, + // }, + // sort: { + // date: sortDesc ? 'desc' : 'asc', + // }, + pagination: { + pageSize, + page: currentPage, + }, + }); + + try { + console.log(`getting /api/vtubers page=${currentPage}`); + const response = await fetch(`${strapiUrl}/api/vtubers?${query}`, fetchVtubersOptions); + + if (!response.ok) { + // Handle non-successful response (e.g., HTTP error) + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const responseData = await response.json(); + + if (!responseData.data || responseData.data.length === 0) { + // No more data to fetch + break; + } + + allVtubers.push(...responseData.data); + currentPage++; + } catch (error) { + // Handle fetch error + if (error instanceof Error) { + console.error('Error fetching data:', error.message); + } + return null; + } + } + + return allVtubers; +} \ No newline at end of file diff --git a/packages/next/app/page.tsx b/packages/next/app/page.tsx new file mode 100644 index 0000000..cd59161 --- /dev/null +++ b/packages/next/app/page.tsx @@ -0,0 +1,72 @@ + +import FundingGoal from "@/components/funding-goal"; +import VodCard from "@/components/vod-card"; +import { getVodTitle } from "@/components/vod-page"; +import { getVods } from '@/lib/vods'; +import { IVod } from "@/lib/vods"; +import { getVtubers, IVtuber } from "./lib/vtubers"; +import VTuberCard from "./components/vtuber-card"; +import Link from 'next/link'; +import { notFound } from "next/navigation"; + +export default async function Page() { + const vods = await getVods(1, 9, true); + const vtubers = await getVtubers(); + if (!vtubers) notFound(); + + // return ( + //
+  //     
+  //       {JSON.stringify(vods.data, null, 2)}
+  //     
+  //   
+ // ) + return ( + <> +
+
+
+

+ The Galaxy's Best VTuber Hentai Site +

+

For adults only (NSFW)

+
+
+ +
+ +
+ +
+ +

Latest VODs

+
+ + {vods.data.map((vod: IVod) => ( + + ))} +
+ + See all Latest Vods +
+
+ +

VTubers

+ {/* */} +
+
+ + ); +} diff --git a/packages/next/app/patrons/page.tsx b/packages/next/app/patrons/page.tsx new file mode 100644 index 0000000..9b10c62 --- /dev/null +++ b/packages/next/app/patrons/page.tsx @@ -0,0 +1,42 @@ + +import PatronsList from '../components/patrons-list'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons"; +import Link from 'next/link' +import { getCampaign } from '../lib/patreon'; + +export default async function Page() { + + const patreonCampaign = await getCampaign() + + return ( + <> +
+
+
+

Patron List

+

+ Futureporn.net continues to improve thanks to + {patreonCampaign.patronCount} generous supporters. +

+ + + +

Want to get your name on this list, and get perks like FAST video streaming?

+ + Become a patron today! + + +

+ Patron names are private by default--{' '} + Opt-in to have your name displayed. +

+
+
+
+ + ); +} diff --git a/packages/next/app/profile/page.tsx b/packages/next/app/profile/page.tsx new file mode 100644 index 0000000..8c5dcdb --- /dev/null +++ b/packages/next/app/profile/page.tsx @@ -0,0 +1,62 @@ +'use client' + +import { useAuth, LoginButton, LogoutButton } from "../components/auth" +import { patreonVideoAccessBenefitId } from "../lib/constants"; +import UserControls from "../components/user-controls"; +import Skeleton, { SkeletonTheme } from "react-loading-skeleton" +import { skeletonHeight, skeletonBorderRadius, skeletonBaseColor, skeletonHighlightColor } from '../lib/constants' + +export default function Page() { + const { authData } = useAuth() + const isLoggedIn = (!!authData?.accessToken) + const isEntitledToCDN = (!!authData?.user?.patreonBenefits.split(',').includes(patreonVideoAccessBenefitId)) + + if (!authData) { + return
+ + + +
+ } + + + return ( + <> +
+ +

{authData?.user?.username} Profile

+ + {/* if not logged in, show login button */} + { + (!authData?.user) && ( + + ) + } + + {/* if logged in and not patron, display welcome */} + { + (!!authData?.accessToken && !isEntitledToCDN) && + <> +

Welcome to Futureporn, {authData?.user?.username || 'chatmember'}! It seems that you are not a patron yet. Please log out and log in again if you believe this is an error. Thank you for your interest!

+ + + } + + {/* if logged in and patron, display profile*/} + { + (!!authData?.user?.patreonBenefits && isEntitledToCDN) && + + } + + + + +
+ + ) +} \ No newline at end of file diff --git a/packages/next/app/streams/[cuid]/not-found.tsx b/packages/next/app/streams/[cuid]/not-found.tsx new file mode 100644 index 0000000..b9d167b --- /dev/null +++ b/packages/next/app/streams/[cuid]/not-found.tsx @@ -0,0 +1,12 @@ +import Link from 'next/link' + +export default function NotFound() { + return ( +
+

404 Not Found

+

Could not find that stream.

+ + Return to streams list +
+ ) +} \ No newline at end of file diff --git a/packages/next/app/streams/[cuid]/page.tsx b/packages/next/app/streams/[cuid]/page.tsx new file mode 100644 index 0000000..e4971e0 --- /dev/null +++ b/packages/next/app/streams/[cuid]/page.tsx @@ -0,0 +1,20 @@ + +import StreamPage from '@/components/stream-page'; +import { getStreamByCuid } from '@/lib/streams'; + + +interface IPageParams { + params: { + cuid: string; + } +} + + +export default async function Page ({ params: { cuid } }: IPageParams) { + const stream = await getStreamByCuid(cuid); + return ( + <> + + + ) +} \ No newline at end of file diff --git a/packages/next/app/streams/page.tsx b/packages/next/app/streams/page.tsx new file mode 100644 index 0000000..1709cc3 --- /dev/null +++ b/packages/next/app/streams/page.tsx @@ -0,0 +1,34 @@ +import Pager from "@/components/pager"; +import StreamsCalendar from "@/components/streams-calendar"; +import StreamsList from "@/components/streams-list"; +import { getAllStreams } from "@/lib/streams"; +import { getAllVtubers } from "@/lib/vtubers"; +import { MissingStaticPage } from "next/dist/shared/lib/utils"; +import { notFound } from "next/navigation"; +// import { useState } from "react"; + + +export default async function Page() { + const vtubers = await getAllVtubers(); + const pageSize = 100; + const page = 1; + if (!vtubers) notFound(); + const missingStreams = await getAllStreams(['missing']); + const issueStreams = await getAllStreams(['issue']); + const goodStreams = await getAllStreams(['good']); + + return ( +
+ {/*
+                
+                    {JSON.stringify(vtubers, null, 2)}
+                
+            
*/} + + + {/* */} + + +
+ ) +} \ No newline at end of file diff --git a/packages/next/app/tags/[slug]/page.tsx b/packages/next/app/tags/[slug]/page.tsx new file mode 100644 index 0000000..04a9d47 --- /dev/null +++ b/packages/next/app/tags/[slug]/page.tsx @@ -0,0 +1,35 @@ +import { getVodsForTag, IVod } from '@/lib/vods' +import VodCard from '@/components/vod-card' +import Link from 'next/link' +import { getVodTitle } from '@/components/vod-page' +import { notFound } from 'next/navigation' + +export default async function Page({ params }: { params: { slug: string }}) { + const vods = await getVodsForTag(params.slug) + if (!vods) return notFound() + return ( +
+
+

Tagged “{params.slug}”

+
+ +
+ {vods.data.map((vod: IVod) => ( + + ))} +
+ +
+

See all tags.

+
+
+ ) +} \ No newline at end of file diff --git a/packages/next/app/tags/page.tsx b/packages/next/app/tags/page.tsx new file mode 100644 index 0000000..77c05b6 --- /dev/null +++ b/packages/next/app/tags/page.tsx @@ -0,0 +1,15 @@ +import { getTags } from '../lib/tags' +import SortableTags from '../components/sortable-tags' + +export default async function Page() { + const tags = await getTags(); + + return ( +
+
+

Tags

+ +
+
+ ) +} \ No newline at end of file diff --git a/packages/next/app/upload/page.tsx b/packages/next/app/upload/page.tsx new file mode 100644 index 0000000..0006657 --- /dev/null +++ b/packages/next/app/upload/page.tsx @@ -0,0 +1,26 @@ + +import { getAllVtubers } from '@/lib/vtubers'; +import UploadForm from '@/components/upload-form'; + +import '@uppy/core/dist/style.min.css'; +import '@uppy/dashboard/dist/style.min.css'; +import { getStreamByCuid } from '@/lib/streams'; + + +export default async function Page() { + + const vtubers = await getAllVtubers(); + + + return ( + <> + + {!vtubers + ? + : + } + + + + ) +} \ No newline at end of file diff --git a/packages/next/app/upload/page.tsx.old b/packages/next/app/upload/page.tsx.old new file mode 100644 index 0000000..80213e3 --- /dev/null +++ b/packages/next/app/upload/page.tsx.old @@ -0,0 +1,240 @@ +'use client' + +import React, { useEffect } from 'react'; +import Uppy from '@uppy/core'; +import { Dashboard } from '@uppy/react'; +import RemoteSources from '@uppy/remote-sources'; +import AwsS3Multipart from '@uppy/aws-s3-multipart'; +import '@uppy/core/dist/style.min.css'; +import '@uppy/dashboard/dist/style.min.css'; +import Image from 'next/image'; +import Link from 'next/link'; + +const uppy = new Uppy() + + + +// uppy.use(AwsS3Multipart, { +// limit: 6, +// companionUrl: process.env.NEXT_PUBLIC_UPPY_COMPANION_URL, +// // companionHeaders: { +// // // @todo +// // // Authorization: `Bearer ${Alpine.store('auth').jwt}` +// // } +// }) + + +// Dashboard, +// { +// inline: true, +// target: '#uppy-dashboard', +// theme: 'auto', +// proudlyDisplayPoweredByUppy: false, +// disableInformer: false, +// // metaFields: [ +// // @todo maybe add meta fields once https://github.com/transloadit/uppy/issues/4427 is fixed +// // { +// // id: 'announceUrl', +// // name: 'Stream Announcement URL', +// // placeholder: 'this is a placeholder' +// // }, +// // { +// // id: 'note', +// // name: 'Note' +// // } +// // { +// // id: 'date', +// // name: 'Stream Date (ISO 8601)', +// // placeholder: '2022-12-30' +// // }, +// // ] +// } +// ) + +// import Uppy from '@uppy/core'; +// import Dashboard from '@uppy/dashboard'; +// import '/@root/node_modules/@uppy/core/dist/style.min.css'; +// import '/@root/node_modules/@uppy/dashboard/dist/style.min.css'; + + + +export default function Page() { + // const dashboard = new Dashboard({ + // inline: true, + // target: '#uppy-dashboard', + // theme: 'dark', + // proudlyDisplayPoweredByUppy: false, + // disableInformer: false, + // }) + + + // useEffect(() => { + // uppy.setOptions({ + // Dashboard: { + // theme: 'dark' + // } + // }) + // }) + + // useEffect(() => { + // uppy.setOptions({ + // restrictions: props.restrictions + // }) + // }, [props.restrictions]) + + // .use( + // Dashboard, + // { + // inline: true, + // target: '#uppy-dashboard', + // theme: 'auto', + // proudlyDisplayPoweredByUppy: false, + // disableInformer: false, + // // metaFields: [ + // // @todo maybe add meta fields once https://github.com/transloadit/uppy/issues/4427 is fixed + // // { + // // id: 'announceUrl', + // // name: 'Stream Announcement URL', + // // placeholder: 'this is a placeholder' + // // }, + // // { + // // id: 'note', + // // name: 'Note' + // // } + // // { + // // id: 'date', + // // name: 'Stream Date (ISO 8601)', + // // placeholder: '2022-12-30' + // // }, + // // ] + // } + // ) + // .use(RemoteSources, { + // companionUrl: process.env.NEXT_PUBLIC_UPPY_COMPANION_URL, + // sources: ['Box', 'OneDrive', 'Dropbox', 'GoogleDrive', 'Url'], + // }) + // .use(AwsS3Multipart, { + // limit: 6, + // companionUrl: process.env.NEXT_PUBLIC_UPPY_COMPANION_URL, + // // companionHeaders: { + // // Authorization: `Bearer ${Alpine.store('auth').jwt}` + // // } + // }) + + return ( + <> + + + ) +} + +// export default function upload () { +// return { +// date: '', +// note: '', +// init () { +// const that = this +// const uppy = new Uppy({ +// onBeforeUpload (files) { +// if (!that.date) { +// const msg = 'File is missing a Stream Date' +// uppy.info(msg, 'error') +// throw new Error(msg) +// } +// }, +// restrictions: { +// maxNumberOfFiles: 1, +// // requiredMetaFields: [ +// // 'announceUrl', +// // 'date' +// // ] +// }, +// }) +// .use( +// Dashboard, +// { +// inline: true, +// target: '#uppy-dashboard', +// theme: 'auto', +// proudlyDisplayPoweredByUppy: false, +// disableInformer: false, +// // metaFields: [ +// // @todo maybe add meta fields once https://github.com/transloadit/uppy/issues/4427 is fixed +// // { +// // id: 'announceUrl', +// // name: 'Stream Announcement URL', +// // placeholder: 'this is a placeholder' +// // }, +// // { +// // id: 'note', +// // name: 'Note' +// // } +// // { +// // id: 'date', +// // name: 'Stream Date (ISO 8601)', +// // placeholder: '2022-12-30' +// // }, +// // ] +// } +// ) +// .use(RemoteSources, { +// companionUrl: window.companionUrl, +// sources: ['Box', 'OneDrive', 'Dropbox', 'GoogleDrive', 'Url'], +// }) +// .use(AwsS3Multipart, { +// limit: 6, +// companionUrl: window.companionUrl, +// companionHeaders: { +// Authorization: `Bearer ${Alpine.store('auth').jwt}` +// } +// }) + + +// uppy.on('file-added', (file) => { +// if (!that.date) { +// uppy.info("Please add the Stream Date to metadata", 'info', 5000) +// } +// }); + + +// uppy.on('complete', (result) => { +// // for each uploaded vod, create a Vod in Strapi +// result.successful.forEach(async (upload) => { +// const res = await fetch(`${Alpine.store('env').backend}/api/vod/createFromUppy`, { +// method: 'POST', +// headers: { +// 'Authorization': `Bearer ${Alpine.store('auth').jwt}`, +// 'Accept': 'application/json', +// 'Content-Type': 'application/json' +// }, +// body: JSON.stringify({ +// data: { +// date: that.date, +// videoSrcB2: { +// key: upload.s3Multipart.key, +// uploadId: upload.s3Multipart.uploadId +// }, +// note: that.note, +// } +// }) +// }) + +// if (res.ok) { +// uppy.info("Thank you. The VOD is queued for approval by a moderator.", 'success', 60000) +// } else { +// uppy.error("There was a problem while uploading. Please try again later.", 'error', 10000) +// } +// }) + +// }) +// } +// } +// } \ No newline at end of file diff --git a/packages/next/app/uppy.tsx b/packages/next/app/uppy.tsx new file mode 100644 index 0000000..64279fc --- /dev/null +++ b/packages/next/app/uppy.tsx @@ -0,0 +1,45 @@ +'use client'; + +import React, { useState, createContext, useContext, useEffect } from 'react'; +import Uppy from '@uppy/core'; +import AwsS3 from '@uppy/aws-s3'; +import RemoteSources from '@uppy/remote-sources'; +import { useAuth } from './components/auth'; + +const companionUrl = process.env.NEXT_PUBLIC_UPPY_COMPANION_URL! + +export const UppyContext = createContext(new Uppy()); + +export default function UppyProvider({ + children +}: { + children: React.ReactNode +}) { + const { authData } = useAuth(); + const [uppy] = useState(() => new Uppy( + { + autoProceed: true + } + ) + .use(RemoteSources, { + companionUrl, + sources: ['GoogleDrive'] + }) + .use(AwsS3, { + companionUrl, + shouldUseMultipart: true, + abortMultipartUpload: () => {}, // @see https://github.com/transloadit/uppy/issues/1197#issuecomment-491756118 + companionHeaders: { + 'authorization': `Bearer ${authData?.accessToken}` + } + }) + ); + + + + return ( + + {children} + + ) +} diff --git a/packages/next/app/vods/[safeDateOrCuid]/page.tsx b/packages/next/app/vods/[safeDateOrCuid]/page.tsx new file mode 100644 index 0000000..3074670 --- /dev/null +++ b/packages/next/app/vods/[safeDateOrCuid]/page.tsx @@ -0,0 +1,16 @@ + +import VodPage from '@/components/vod-page'; +import { IVodPageProps, getVodFromSafeDateOrCuid } from '@/lib/vods'; +import { notFound } from 'next/navigation'; + + +/** + * This route exists as backwards compatibility for Futureporn 0.0.0 which was on neocities + * @see https://www.w3.org/Provider/Style/URI + */ +export default async function Page({ params: { safeDateOrCuid, slug } }: IVodPageProps) { + const vod = await getVodFromSafeDateOrCuid(safeDateOrCuid); + if (!vod) notFound(); + return +} + diff --git a/packages/next/app/vods/page.tsx b/packages/next/app/vods/page.tsx new file mode 100644 index 0000000..175ff08 --- /dev/null +++ b/packages/next/app/vods/page.tsx @@ -0,0 +1,6 @@ + +import { redirect } from 'next/navigation'; + +export default async function Page() { + redirect('/latest-vods/1') +} \ No newline at end of file diff --git a/packages/next/app/vt/[slug]/history/page.tsx b/packages/next/app/vt/[slug]/history/page.tsx new file mode 100644 index 0000000..4c25482 --- /dev/null +++ b/packages/next/app/vt/[slug]/history/page.tsx @@ -0,0 +1,70 @@ + +import { getVtuberBySlug } from '@/lib/vtubers'; +import { getAllStreamsForVtuber } from '@/lib/streams'; +import NotFound from '../not-found'; +import { DataRecord } from 'cal-heatmap/src/options/Options'; +import { Cal } from '@/components/cal'; + +interface IPageProps { + params: { + slug: string; + }; +} + +function getArchiveStatusValue(archiveStatus: string): number { + if (archiveStatus === 'good') return 2; + if (archiveStatus === 'issue') return 1; + else return 0 // missing +} + +function sortDataRecordsByDate(records: DataRecord[]) { + return records.sort((a, b) => { + if (typeof a.date === 'string' && typeof b.date === 'string') { + return a.date.localeCompare(b.date); + } else { + // Handle comparison when date is not a string (e.g., when it's a number) + // For instance, you might want to convert numbers to strings or use a different comparison logic. + // Example assuming number to string conversion: + return String(a.date).localeCompare(String(b.date)); + } + }); +} + + +export default async function Page({ params: { slug } }: IPageProps) { + const vtuber = await getVtuberBySlug(slug); + if (!vtuber) return + const streams = await getAllStreamsForVtuber(vtuber.id); + const streamsByYear: { [year: string]: DataRecord[] } = {}; + streams.forEach((stream) => { + const date = new Date(stream.attributes.date); + const year = date.getFullYear(); + if (!streamsByYear[year]) { + streamsByYear[year] = []; + } + streamsByYear[year].push({ + date: new Date(stream.attributes.date).toISOString(), + value: stream.attributes.archiveStatus, + }); + }); + // Sort the data records within each year's array + for (const year in streamsByYear) { + streamsByYear[year] = sortDataRecordsByDate(streamsByYear[year]); + } + + + return ( +
+ {Object.keys(streamsByYear).map((year) => { + return ( +
+

{year}

+ {/*
{JSON.stringify(streamsByYear[year], null, 2)}
*/} + +
+ ) + })} + +
+ ) +} diff --git a/packages/next/app/vt/[slug]/not-found.tsx b/packages/next/app/vt/[slug]/not-found.tsx new file mode 100644 index 0000000..8027f63 --- /dev/null +++ b/packages/next/app/vt/[slug]/not-found.tsx @@ -0,0 +1,12 @@ +import Link from 'next/link' + +export default function NotFound() { + return ( +
+

404 Not Found

+

Could not find a matching vtubler.

+ + Return to vtuber list +
+ ) +} \ No newline at end of file diff --git a/packages/next/app/vt/[slug]/page.tsx b/packages/next/app/vt/[slug]/page.tsx new file mode 100644 index 0000000..910cb64 --- /dev/null +++ b/packages/next/app/vt/[slug]/page.tsx @@ -0,0 +1,246 @@ +import VodsList from '@/components/vods-list'; +import Link from 'next/link'; +import { getVtuberBySlug } from '@/lib/vtubers' +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faExternalLinkAlt, faBagShopping } from "@fortawesome/free-solid-svg-icons"; +import { faFacebook, faInstagram, faPatreon, faYoutube, faTwitch, faTiktok, faXTwitter, faReddit, faDiscord } from "@fortawesome/free-brands-svg-icons"; +import Image from 'next/image'; +import OnlyfansIcon from "@/components/icons/onlyfans"; +import PornhubIcon from '@/components/icons/pornhub'; +import ThroneIcon from '@/components/icons/throne'; +import LinktreeIcon from '@/components/icons/linktree'; +import FanslyIcon from '@/components/icons/fansly'; +import ChaturbateIcon from '@/components/icons/chaturbate'; +import CarrdIcon from '@/components/icons/carrd'; +import styles from '@/assets/styles/icon.module.css'; + +import { getVodsForVtuber } from '@/lib/vods'; +import { notFound } from 'next/navigation'; +import ArchiveProgress from '@/components/archive-progress'; +import StreamsCalendar from '@/components/streams-calendar'; +import { getAllStreamsForVtuber, getStreamsForVtuber } from '@/lib/streams'; +import LinkableHeading from '@/components/linkable-heading'; + + + +export default async function Page({ params }: { params: { slug: string } }) { + const vtuber = await getVtuberBySlug(params.slug); + if (!vtuber) notFound(); + + const vods = await getVodsForVtuber(vtuber.id, 1, 9); + if (!vods) notFound(); + + const missingStreams = await getAllStreamsForVtuber(vtuber.id, ['missing']); + const issueStreams = await getAllStreamsForVtuber(vtuber.id, ['issue']); + const goodStreams = await getAllStreamsForVtuber(vtuber.id, ['good']); + + + + // return ( + // <> + //

hi mom!

+ //
+  //       
+  //         {JSON.stringify(missingStreams, null, 2)}
+  //       
+  //     
+ // + // ) + + return ( + <> + {vtuber && ( + <> +
+ +
+
+

{vtuber.attributes.displayName}

+
+
+
+ {vtuber.attributes.displayName} +
+
+
+

{vtuber.attributes.description1}

+

{vtuber.attributes.description2}

+
+
+ +

+ Socials +

+ + +
+
+ {vtuber.attributes.patreon && ( +
+ + Patreon + +
+ )} + {vtuber.attributes.twitter && ( +
+ + Twitter + +
+ )} + {vtuber.attributes.youtube && ( +
+ + YouTube + +
+ )} + {vtuber.attributes.twitch && ( +
+ + Twitch + +
+ )} + {vtuber.attributes.tiktok && ( +
+ + TikTok + +
+ )} + {vtuber.attributes.fansly && ( +
+ + Fansly + +
+ )} + {vtuber.attributes.onlyfans && ( +
+ + + + OnlyFans + +
+ )} + {vtuber.attributes.pornhub && ( +
+ + Pornhub + +
+ )} + {vtuber.attributes.reddit && ( +
+ + Reddit + +
+ )} + {vtuber.attributes.discord && ( +
+ + Discord + +
+ )} + {vtuber.attributes.instagram && ( +
+ + Instagram + +
+ )} + {vtuber.attributes.facebook && ( +
+ + Facebook + +
+ )} + {vtuber.attributes.merch && ( +
+ + Merch + +
+ )} + {vtuber.attributes.chaturbate && ( +
+ + Chaturbate + +
+ )} + {vtuber.attributes.throne && ( +
+ + Throne + +
+ )} + {vtuber.attributes.linktree && ( +
+ + Linktree + +
+ )} + {vtuber.attributes.carrd && ( +
+ + Carrd + +
+ )} +
+
+ + + {/*

+ Toys +

+ + <> + + {(toys.pagination.total > toySampleCount) && See all of {vtuber.displayName}'s toys} + */} + +

+ Vods +

+ + + { + (vtuber.attributes.vods) ? ( + See all {vtuber.attributes.displayName} vods + ) : (

No VODs have been added, yet.

) + } + +

+ Streams +

+ +{/* +

+ Archive Progress +

+ */} + + +
+ + )} + + ); +} diff --git a/packages/next/app/vt/[slug]/stream/[safeDate]/page.tsx b/packages/next/app/vt/[slug]/stream/[safeDate]/page.tsx new file mode 100644 index 0000000..4d1f71f --- /dev/null +++ b/packages/next/app/vt/[slug]/stream/[safeDate]/page.tsx @@ -0,0 +1,31 @@ + +import { Stream } from '@/components/stream'; +import { IStream, getStreamForVtuber } from '@/lib/streams'; +import { getVtuberBySlug } from '@/lib/vtubers'; +import NotFound from '../../not-found'; + +interface IPageProps { + params: { + safeDate: string; + slug: string; + }; +} + +export default async function Page({ params: { safeDate, slug } }: IPageProps) { + const vtuber = await getVtuberBySlug(slug); + if (!vtuber) return + const stream = await getStreamForVtuber(vtuber.id, safeDate); + if (!stream) return + + return ( +
+
+

Stream Page!

+

slug={slug} safeDate={safeDate}

+ + +
+
+ ) +} + diff --git a/packages/next/app/vt/[slug]/streams/page.tsx b/packages/next/app/vt/[slug]/streams/page.tsx new file mode 100644 index 0000000..6e6bdff --- /dev/null +++ b/packages/next/app/vt/[slug]/streams/page.tsx @@ -0,0 +1,23 @@ + +import { getVtuberBySlug } from '@/lib/vtubers'; +import { getStreamsForVtuber } from '@/lib/streams'; +import Pager from '@/components/pager'; +import { notFound } from 'next/navigation'; + +interface IPageParams { + params: { + slug: string; + } +} + +export default async function Page({ params }: IPageParams) { + const vtuber = await getVtuberBySlug(params.slug); + if (!vtuber) return

vtuber {params.slug} not found

+ const streams = await getStreamsForVtuber(vtuber.id, 1, 24); + if (!streams) return

streams not found

; + return ( + <> + + + ) +} \ No newline at end of file diff --git a/packages/next/app/vt/[slug]/toys/[page]/page.tsx b/packages/next/app/vt/[slug]/toys/[page]/page.tsx new file mode 100644 index 0000000..f29c912 --- /dev/null +++ b/packages/next/app/vt/[slug]/toys/[page]/page.tsx @@ -0,0 +1,33 @@ + +// import VodsList, { VodsListHeading } from '@/components/vods-list' +// import { getVtuberBySlug } from '@/lib/vtubers' +// // import { IToys, getToysForVtuber } from '@/lib/toys' +// import { ToysList, ToysListHeading } from '@/components/toys' +// import Pager from '@/components/pager' + +// interface IPageParams { +// params: { +// name: string; +// page: number; +// } +// } + +export default async function Page() { + // const vtuber = await getVtuberBySlug(params.slug) + return

Toys pages coming soon

+ // const toys: IToys = await getToysForVtuber(vtuber.id, params.page, 24) + // return ( + //
+ //
+ // + // + // + //
+ //
+ // ) +} \ No newline at end of file diff --git a/packages/next/app/vt/[slug]/toys/page.tsx b/packages/next/app/vt/[slug]/toys/page.tsx new file mode 100644 index 0000000..e1b8843 --- /dev/null +++ b/packages/next/app/vt/[slug]/toys/page.tsx @@ -0,0 +1,33 @@ + +// import VodsList, { VodsListHeading } from '@/components/vods-list' +// import { getVtuberBySlug } from '@/lib/vtubers' +// // import { IToys, getToysForVtuber } from '@/lib/toys' +// import { ToysList } from '@/components/toys' +// import Pager from '@/components/pager' + +interface IPageParams { + params: { + name: string; + } +} + +export default async function Page({ params }: IPageParams) { + // const vtuber = await getVtuberBySlug(params.slug) + return

toys pages coming soon

+ // const toys: IToys = await getToysForVtuber(vtuber.id, 1, 24) + // return ( + //
+ //
+ // {/* */} + // {/* */} + // + // + //
+ //
+ // ) +} \ No newline at end of file diff --git a/packages/next/app/vt/[slug]/vod/[safeDateOrCuid]/page.tsx b/packages/next/app/vt/[slug]/vod/[safeDateOrCuid]/page.tsx new file mode 100644 index 0000000..c5e615c --- /dev/null +++ b/packages/next/app/vt/[slug]/vod/[safeDateOrCuid]/page.tsx @@ -0,0 +1,12 @@ + +import VodPage from '@/components/vod-page' +import { IVodPageProps, getVodFromSafeDateOrCuid } from '@/lib/vods' +import { notFound } from 'next/navigation'; + + +export default async function Page({ params: { safeDateOrCuid } }: IVodPageProps) { + const vod = await getVodFromSafeDateOrCuid(safeDateOrCuid); + if (!vod) return notFound(); + return +} + diff --git a/packages/next/app/vt/[slug]/vod/page.tsx b/packages/next/app/vt/[slug]/vod/page.tsx new file mode 100644 index 0000000..b62229f --- /dev/null +++ b/packages/next/app/vt/[slug]/vod/page.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import Link from 'next/link'; +import { redirect } from 'next/navigation'; + +interface IPageParams { + params: { + slug: string; + } +} + +export default function Page({ params: { slug } }: IPageParams) { + redirect(`/vt/${slug}/vods`) + return See {`/vt/${slug}/vods`} +} + diff --git a/packages/next/app/vt/[slug]/vods/[page]/page.tsx b/packages/next/app/vt/[slug]/vods/[page]/page.tsx new file mode 100644 index 0000000..4bd6aea --- /dev/null +++ b/packages/next/app/vt/[slug]/vods/[page]/page.tsx @@ -0,0 +1,43 @@ +import VodsList, { VodsListHeading } from '@/components/vods-list'; +import { getVtuberBySlug, getUrl } from '@/lib/vtubers'; +import { IVodsResponse, getVodsForVtuber } from '@/lib/vods'; +import Pager from '@/components/pager'; +import { notFound } from 'next/navigation'; + + +interface IPageParams { + params: { + slug: string; + page: string; + }; +} + +export default async function Page({ params }: IPageParams) { + let vtuber, vods; + const pageNumber = parseInt(params.page); + + try { + vtuber = await getVtuberBySlug(params.slug); + if (!vtuber) notFound(); + vods = await getVodsForVtuber(vtuber.id, pageNumber, 24, true); + } catch (error) { + // Handle the error here (e.g., display an error message) + console.error("An error occurred:", error); + // You might also want to return an error page or message + return
Error: {JSON.stringify(error)}
; + } + + + if (!vods) return

error

+ return ( + <> + + + + + ); +} diff --git a/packages/next/app/vt/[slug]/vods/page.tsx b/packages/next/app/vt/[slug]/vods/page.tsx new file mode 100644 index 0000000..3c40c7a --- /dev/null +++ b/packages/next/app/vt/[slug]/vods/page.tsx @@ -0,0 +1,26 @@ + +import VodsList, { VodsListHeading } from '@/components/vods-list' +import { getVtuberBySlug, getUrl } from '@/lib/vtubers' +import { IVodsResponse, getVodsForVtuber, getPaginatedUrl } from '@/lib/vods' +import Pager from '@/components/pager' +import { notFound } from 'next/navigation' + +interface IPageParams { + params: { + slug: string; + } +} + +export default async function Page({ params }: IPageParams) { + const vtuber = await getVtuberBySlug(params.slug) + if (!vtuber) notFound(); + const vods = await getVodsForVtuber(vtuber.id, 1, 24) + if (!vods) notFound(); + return ( + <> + + + + + ) +} \ No newline at end of file diff --git a/packages/next/app/vt/page.tsx b/packages/next/app/vt/page.tsx new file mode 100644 index 0000000..9e410eb --- /dev/null +++ b/packages/next/app/vt/page.tsx @@ -0,0 +1,30 @@ +import { notFound } from 'next/navigation' +import VTuberCard from '../components/vtuber-card' +import { getVtubers, IVtuber } from '../lib/vtubers' + + +export default async function Page() { + const vtubers = await getVtubers() + if (!vtubers) notFound() + // return ( + //
+    //         
+    //             {JSON.stringify(vtubers, null, 2)}
+    //         
+    //     
+ // ) + return ( + <> +
+
+

VTubers

+ +
+
+ + ) +} \ No newline at end of file diff --git a/packages/next/assets/styles/calendar-heatmap.module.scss b/packages/next/assets/styles/calendar-heatmap.module.scss new file mode 100644 index 0000000..2e8f8bf --- /dev/null +++ b/packages/next/assets/styles/calendar-heatmap.module.scss @@ -0,0 +1,89 @@ +$cell-height : 10px; +$cell-width : 10px; +$cell-margin:2px; +$cell-weekdays-width: 30px; + +html { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +} + +html, body { + height: 100%; + width: 100%; +} + +#container { + height: 514px; + width: 930px; + margin: 50px auto; +} + +.timeline { + margin: 20px; + margin-bottom: 60px; + + .timeline-months { + display: flex; + padding-left: $cell-weekdays-width; + + &-month { + width: $cell-width; + margin: $cell-margin; + border: 1px solid transparent; + font-size: 10px; + } + + .Jan~.Jan, + .Feb~.Feb, + .Mar~.Mar, + .Apr~.Apr, + .May~.May, + .Jun~.Jun, + .Jul~.Jul, + .Aug~.Aug, + .Sep~.Sep, + .Oct~.Oct, + .Nov~.Nov, + .Dec~.Dec { + visibility: hidden; + } + } + + &-body { + display: flex; + + .timeline-weekdays { + display: inline-flex; + flex-direction: column; + width: $cell-weekdays-width; + + &-weekday { + font-size: 10px; + height: $cell-height; + border: 1px solid transparent; + margin: $cell-margin; + vertical-align: middle; + } + } + + .timeline-cells { + display: inline-flex; + flex-direction: column; + flex-wrap: wrap; + height: #{(10 + 4) * 8}px; + + &-cell { + height: $cell-height; + width: $cell-width; + border: 1px solid rgba(0, 0, 0, 0.1); + margin: $cell-margin; + border-radius: 2px; + background-color: rgba(0, 0, 0, 0.05); + + &:hover { + border: 1px solid rgba(0, 0, 0, 0.3); + } + } + } + } +} \ No newline at end of file diff --git a/packages/next/assets/styles/cid.module.css b/packages/next/assets/styles/cid.module.css new file mode 100644 index 0000000..e195bb4 --- /dev/null +++ b/packages/next/assets/styles/cid.module.css @@ -0,0 +1,19 @@ +.container { + display: flex; + align-items: center; +} + +.cid { + overflow: hidden; + text-overflow: ellipsis; + text-align: center; + flex: 1; +} + +.label { + width: 6em; +} + +.green { + color: rgb(52, 168, 115); +} \ No newline at end of file diff --git a/packages/next/assets/styles/fp.module.css b/packages/next/assets/styles/fp.module.css new file mode 100644 index 0000000..946e963 --- /dev/null +++ b/packages/next/assets/styles/fp.module.css @@ -0,0 +1,20 @@ + +.noselect { + user-select: none; +} + +.tagButton { + height: 2em; + margin-bottom: 0.5rem; + border-radius: 4px; +} + +.isTiny { + height: 1.5em; +} + +.grade { + font-family: Arial, Helvetica, sans-serif; + font-size: 8rem; + font-weight: bolder; +} \ No newline at end of file diff --git a/packages/next/assets/styles/global.sass b/packages/next/assets/styles/global.sass new file mode 100644 index 0000000..7057e6d --- /dev/null +++ b/packages/next/assets/styles/global.sass @@ -0,0 +1,50 @@ +@charset "utf-8" + +// Import a Google Font +@import url('https://fonts.googleapis.com/css?family=Nunito:400,700') + +body + background-color: rgb(23, 24, 28) + + +// Set your brand colors +$purple: #8A4D76 +$pink: #FA7C91 +$brown: #757763 +$beige-light: #D0D1CD +$beige-lighter: #EFF0EB + +// Update Bulma's global variables +$family-sans-serif: "Nunito", sans-serif +$grey-dark: $brown +$grey-light: $beige-light +$primary: $purple +$link: $pink +$widescreen-enabled: false +$fullhd-enabled: false + +// Update some of Bulma's component variables +$body-background-color: $beige-lighter +$control-border-width: 2px +$input-border-color: transparent +$input-shadow: none + +// Import only what you need from Bulma +// @import "../node_modules/bulma/sass/utilities/_all.sass" +// @import "../node_modules/bulma/sass/base/_all.sass" +// @import "../node_modules/bulma/sass/elements/button.sass" +// @import "../node_modules/bulma/sass/elements/container.sass" +// @import "../node_modules/bulma/sass/elements/title.sass" +// @import "../node_modules/bulma/sass/form/_all.sass" +// @import "../node_modules/bulma/sass/components/navbar.sass" +// @import "../node_modules/bulma/sass/layout/hero.sass" +// @import "../node_modules/bulma/sass/layout/section.sass" + +@import "../../node_modules/bulma/bulma.sass" + +@import "../../node_modules/bulma-prefers-dark/bulma-prefers-dark.sass" + +a.navbar-item:active, +a.navbar-item:focus, +a.navbar-item:focus-within + background-color: hsl(0, 0%, 20%) \ No newline at end of file diff --git a/packages/next/assets/styles/icon.module.css b/packages/next/assets/styles/icon.module.css new file mode 100644 index 0000000..5d3efba --- /dev/null +++ b/packages/next/assets/styles/icon.module.css @@ -0,0 +1,20 @@ + +svg.icon { + width: 1em; + height: 1em; + vertical-align: -0.125em; + fill: rgb(208, 209, 205); +} + +svg.icon path { + fill: rgb(208, 209, 205); +} + +svg.icon g path { + fill: rgb(208, 209, 205) !important; +} + +svg.bigIcon { + width: 10em; + height: 10em; +} \ No newline at end of file diff --git a/packages/next/assets/styles/player.module.css b/packages/next/assets/styles/player.module.css new file mode 100644 index 0000000..b3f531b --- /dev/null +++ b/packages/next/assets/styles/player.module.css @@ -0,0 +1,4 @@ + +.fpMediaPlayer { + --media-aspect-ratio: 1.7778; +} \ No newline at end of file diff --git a/packages/next/assets/svg/README.md b/packages/next/assets/svg/README.md new file mode 100644 index 0000000..bbf6f41 --- /dev/null +++ b/packages/next/assets/svg/README.md @@ -0,0 +1,5 @@ +# SVG in next/react + +see https://blog.logrocket.com/import-svgs-next-js-apps/ + +TL;DR: use https://react-svgr.com/playground/ to convert svg to jsx \ No newline at end of file diff --git a/packages/next/assets/svg/carrd.svg b/packages/next/assets/svg/carrd.svg new file mode 100644 index 0000000..9b1ead5 --- /dev/null +++ b/packages/next/assets/svg/carrd.svg @@ -0,0 +1 @@ +Carrd diff --git a/packages/next/assets/svg/chaturbate.svg b/packages/next/assets/svg/chaturbate.svg new file mode 100644 index 0000000..0ef00ed --- /dev/null +++ b/packages/next/assets/svg/chaturbate.svg @@ -0,0 +1,15 @@ +import * as React from "react" +const SvgComponent = (props) => ( + + + +) +export default SvgComponent diff --git a/packages/next/assets/svg/checkmark.svg b/packages/next/assets/svg/checkmark.svg new file mode 100644 index 0000000..23cce6f --- /dev/null +++ b/packages/next/assets/svg/checkmark.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/next/assets/svg/fansly.tsx b/packages/next/assets/svg/fansly.tsx new file mode 100644 index 0000000..fcf575d --- /dev/null +++ b/packages/next/assets/svg/fansly.tsx @@ -0,0 +1,20 @@ +import * as React from "react" +const SvgComponent = (props) => ( + + + + +) +export default SvgComponent diff --git a/packages/next/assets/svg/ipfs.svg b/packages/next/assets/svg/ipfs.svg new file mode 100644 index 0000000..ea32d6e --- /dev/null +++ b/packages/next/assets/svg/ipfs.svg @@ -0,0 +1 @@ +IPFS \ No newline at end of file diff --git a/packages/next/assets/svg/linktree.svg b/packages/next/assets/svg/linktree.svg new file mode 100644 index 0000000..0f8d400 --- /dev/null +++ b/packages/next/assets/svg/linktree.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + diff --git a/packages/next/assets/svg/noun-adult-content-1731184.svg b/packages/next/assets/svg/noun-adult-content-1731184.svg new file mode 100644 index 0000000..8509fd0 --- /dev/null +++ b/packages/next/assets/svg/noun-adult-content-1731184.svg @@ -0,0 +1 @@ +Created by Anatolii Babiifrom the Noun Project \ No newline at end of file diff --git a/packages/next/assets/svg/noun-anime-3890912.svg b/packages/next/assets/svg/noun-anime-3890912.svg new file mode 100644 index 0000000..c5c376f --- /dev/null +++ b/packages/next/assets/svg/noun-anime-3890912.svg @@ -0,0 +1 @@ +Created by Kevinfrom the Noun Project \ No newline at end of file diff --git a/packages/next/assets/svg/noun-avatar-3546974.svg b/packages/next/assets/svg/noun-avatar-3546974.svg new file mode 100644 index 0000000..ec43ede --- /dev/null +++ b/packages/next/assets/svg/noun-avatar-3546974.svg @@ -0,0 +1 @@ +love charger copy 2Created by KEN111from the Noun Project \ No newline at end of file diff --git a/packages/next/assets/svg/noun-girl-842331.svg b/packages/next/assets/svg/noun-girl-842331.svg new file mode 100644 index 0000000..f609fb4 --- /dev/null +++ b/packages/next/assets/svg/noun-girl-842331.svg @@ -0,0 +1,4 @@ +Created by Zackary Cloefrom the Noun Project \ No newline at end of file diff --git a/packages/next/assets/svg/noun-network-1603820.svg b/packages/next/assets/svg/noun-network-1603820.svg new file mode 100644 index 0000000..8a3d5c0 --- /dev/null +++ b/packages/next/assets/svg/noun-network-1603820.svg @@ -0,0 +1 @@ +Created by Three Six Fivefrom the Noun Project \ No newline at end of file diff --git a/packages/next/assets/svg/onlyfans.svg b/packages/next/assets/svg/onlyfans.svg new file mode 100644 index 0000000..bba261f --- /dev/null +++ b/packages/next/assets/svg/onlyfans.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/packages/next/assets/svg/pornhub.svg b/packages/next/assets/svg/pornhub.svg new file mode 100644 index 0000000..4b1b6bd --- /dev/null +++ b/packages/next/assets/svg/pornhub.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/next/assets/svg/throne.svg b/packages/next/assets/svg/throne.svg new file mode 100644 index 0000000..a396573 --- /dev/null +++ b/packages/next/assets/svg/throne.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/next/next.config.js b/packages/next/next.config.js new file mode 100644 index 0000000..1706724 --- /dev/null +++ b/packages/next/next.config.js @@ -0,0 +1,22 @@ +/** @type {import('next').NextConfig} */ +const path = require("path"); +const nextConfig = { + output: 'standalone', + reactStrictMode: false, + sassOptions: { + includePaths: [path.join(__dirname, "assets", "styles")], + }, + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 'futureporn-b2.b-cdn.net', + port: '', + pathname: '/**', + }, + ], + } +}; + + +module.exports = nextConfig; diff --git a/packages/next/package.json b/packages/next/package.json new file mode 100644 index 0000000..f97e6e2 --- /dev/null +++ b/packages/next/package.json @@ -0,0 +1,79 @@ +{ + "name": "fp-next", + "version": "2.0.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "preinstall": "npx only-allow pnpm" + }, + "dependencies": { + "@fortawesome/fontawesome-free": "^6.5.1", + "@fortawesome/fontawesome-svg-core": "^6.5.1", + "@fortawesome/free-brands-svg-icons": "^6.5.1", + "@fortawesome/free-solid-svg-icons": "^6.5.1", + "@fortawesome/react-fontawesome": "^0.2.0", + "@fullcalendar/core": "^6.1.10", + "@fullcalendar/daygrid": "^6.1.10", + "@fullcalendar/interaction": "^6.1.10", + "@fullcalendar/multimonth": "^6.1.10", + "@fullcalendar/react": "^6.1.10", + "@hookform/error-message": "^2.0.1", + "@hookform/resolvers": "^3.3.4", + "@mux/blurhash": "^0.1.2", + "@mux/mux-player": "^2.3.1", + "@mux/mux-player-react": "^2.3.1", + "@paralleldrive/cuid2": "^2.2.2", + "@react-hookz/web": "^24.0.2", + "@types/lodash": "^4.14.202", + "@types/qs": "^6.9.11", + "@types/react": "^18.2.47", + "@types/react-dom": "^18.2.18", + "@uppy/aws-s3": "^3.6.0", + "@uppy/aws-s3-multipart": "^3.3.0", + "@uppy/core": "^3.8.0", + "@uppy/dashboard": "^3.7.1", + "@uppy/drag-drop": "^3.0.3", + "@uppy/file-input": "^3.0.4", + "@uppy/progress-bar": "^3.0.4", + "@uppy/react": "^3.2.1", + "@uppy/remote-sources": "^1.1.0", + "bulma": "^0.9.4", + "bulma-prefers-dark": "0.1.0-beta.1", + "cal-heatmap": "^4.2.4", + "date-fns": "^2.0.0", + "date-fns-tz": "^2.0.0", + "dayjs": "^1.11.10", + "feed": "^4.2.2", + "gray-matter": "^4.0.3", + "hls.js": "^1.5.1", + "lodash": "^4.17.21", + "lunarphase-js": "^2.0.1", + "multiformats": "^13.0.1", + "next": "^14.0.4", + "next-goatcounter": "^1.0.5", + "nextjs-toploader": "^1.6.4", + "plyr": "^3.7.8", + "plyr-react": "^5.3.0", + "prism-react-renderer": "^2.3.1", + "qs": "^6.11.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hook-form": "^7.49.3", + "react-loading-skeleton": "^3.3.1", + "react-toastify": "^9.1.3", + "sass": "^1.69.7", + "sharp": "^0.33.2", + "slugify": "^1.6.6", + "yup": "^1.3.3" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "eslint": "^8.56.0", + "eslint-config-next": "14.0.4", + "tsc": "^2.0.4", + "typescript": "5.3.3" + } +} diff --git a/packages/next/public/futureporn-icon.png b/packages/next/public/futureporn-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..037e639e49e8b4ebe1835af95607fdeaa0a8cfcc GIT binary patch literal 11599 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 N002ovPDHLkV1jjAs`dZ? literal 0 HcmV?d00001 diff --git a/packages/next/public/images/.keep b/packages/next/public/images/.keep new file mode 100644 index 0000000..e69de29 diff --git a/packages/next/public/images/cj_clippy.jpg b/packages/next/public/images/cj_clippy.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d1c871709a662ad970baff230eeb0aaac0d0033d GIT binary patch literal 301528 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%0DjIdK=y8$iQcN09xV#{Le#B-}*0c4|c_=&z%_ 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 z

    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/v9jQX!l=}lr|=}-hugqRf|bSD8&fD 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

    {e9>*03wERwsD2hs(^p05QPjjZqe^ z^F1k-VC7>wMK5n0etLmp(07Ujqc`KsNW_Ud+ddH~gQYDnbV!lKT2ba-Qmwin*+B0K ze({(4C6WH6s9o4R8+1tGF)A^MZxG^@qLQ@in%?WK!5H;5VzyKzgWhN6uzIcuVpT<+ z$}u)M=B!2?@#Ow;4QL;zoQnW=fP%3^W$hRE{{S(hxww>@8U>dvf|SDTHO%fO;Wol=o#Cp|myA1G#jJ zkyC2!^}pdd3>YxzTx(i%C(|(P1AH)4OYs3(8@KV14gIka?zFY%Q!R)Fg{yw=W7~r@ zg#J~!cV1xkD+CPuA4mleS_y){iPl`^FvHTlOWB5t@P?{85qOAe1E`VJS%Y`w_!%8o`?kbW8My`ZxsHe%a<-(S#f%?{u;X1-ZcBeYOJkuU&-w; zs;#O-ei+&t?CvFg_Il`&n<%v1&xw!HfA(Y6Rja&CVqmm44(|Oz%MPOSP^?|8Q6fZ+ zsdazZDYgYSHh*6o2Sy;M1x9fcRe$G0`((s{4-RfhcflJ35gYQcVGG>^#7& zU9;Jm*H<&)l;JvP4+OMF4gnBSp>vq{=w+#fuVqvHN^==myML;SS-2YaMd%bn=F<`} z?-0fa)d_5;SQ73d&;mql8MMD?+Kg>GGaaDfZ)(@z{LC{gt;NkZ67C|!Q>R1H&RBMf zAHn@6@9Msz^p8t}E)1)}1N(b#Djpi7;>_P;ROSE|^N+kmuu&QZ{zV13!ejFdIit$( z4cx~zyXCt4X$0I~w8L`i^av0pSI6%%m|%lBl^Tg2pfxCZ0OZdyk|uUwN}_DKbrZPK zOb9O&ykGCLXq5z>!T~3j7XgmqC|-jBtg^*FTBEyW5>{v%m=|tJcytfvKbBu?;#H zW=2@6y&Z?dR8@7m{ZI%QruZp~05UXv$Iu1`sE<@PFG1anAZ;hJ9Us|4f&C?4BhaCY z22Y8v+&mJLYJq>aU>G$p{wA#kZG9Ue&7{0Ox}gHW8ika!34*!pHkItS7n8Xzn2iD= z3>_e{p<-M@EK{6gSBGuJX^MiO&}tAf3|cd2F4EbkM?r4idHh$z(j7`56U$fIxeTD? zuu=GnYhs=T58%Fo^xsxAWdUpF(+ApVd8R2Sm)N#dJ)IV4LHm2TdPhyiOPA51J30SDjX_RfV*}7;!lV?lc-yR|7Yek93%lcUV03C~R z&rV3-+baW7FL3W=q}4SJImQ0q&?yU4&_no~Gh5D~x6C$j_Js?Wks}gYPbV>~+`iK1 z$!{%cGoJY zsty3rRLaJSz9R`__%3C_aq9m2K;o6G2QF7t-Kx$R`3+9wY&s^QU(6$cIuSD+^|l~7 zKCw*{k4=A4vL;N~uJtbN#)h#S#CnOQ5ZGg8zYs2;iB*rpKLQ0E52Z*l$Vlxt6gJxu zh|!uBt-<{$Rbbb-IQ}c>KTA+*Kp)`vx3nnQ%Y0vvqqKZЊz{f~P?@(Pn>+Tt3 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⪚+kOv9^AbDhvk~MO_<>Ffe<XQvY@2UEdp& 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 A#{d8T literal 0 HcmV?d00001 diff --git a/packages/next/public/images/default-thumbnail.webp b/packages/next/public/images/default-thumbnail.webp new file mode 100644 index 0000000000000000000000000000000000000000..d468e1550ec6e84e3e761c3e5119bff94154a26e GIT binary patch literal 27268 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 literal 0 HcmV?d00001 diff --git a/packages/next/public/images/projekt-melody.jpg b/packages/next/public/images/projekt-melody.jpg new file mode 100644 index 0000000000000000000000000000000000000000..41916fa3768fd0529734e64fb57f8e5ab153ebef GIT binary patch literal 134526 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%0A)eECV+`4H>9@P74TTE)R1~TqPV- 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

    1=I6< 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 P@G>1CM&R)M-`f8HJgPzT literal 0 HcmV?d00001 diff --git a/packages/next/public/images/projektmelody-thumbnail.webp b/packages/next/public/images/projektmelody-thumbnail.webp new file mode 100644 index 0000000000000000000000000000000000000000..dfb8073fb7905d5cd3059b66a46c0e0e0a40d428 GIT binary patch literal 1356 zcmV-S1+)56Nk&FQ1pok7MM6+kP&gns1pojr6#$(9DnI~106uLjlSU*Wp`k8#>ga$C ziD&?wGQEFt>=Nqj=+$8$FZ9p6>fxFwl07pL8V zr&lbSZ#7=~ZiZJ|55`TYh_fMoungy(GqZO>;=~qdcISl&;^r_(!Q#`JS8Zr$Q~>__ zpC=52Sx6W;ePns+Dq>aZI7vS-W=jyHm6F>;5lS%}@z-p*5QRF^4Z208cT_(?H^f6c z51H~Qo$edi2A4Yflb_hq0sVx}{*>*n{vQ~EPYgYnZFn_6{0BS)ms1U;KQC<gA2Um(TKH6+ zQF84>a6TXkAT@4EXR^JEmE}+_qgd>=A4hs@h}eS#IA6JO1}c*{fKY ztYb!KY#PLo9)&NE*us)dg$5OrHfcouhsm37ZRYy0_&xE?f0 zSq6mVP~ay_?3?$^!M%T!Iwg1GO&@l1DL}6u=ikY&!eD~u807=>RDdmV@Ls2wT&NCE zMk>v*k}IoX-;^>UD!_ye9i#3*}^JEx# zttRC}DaDF%?ex6UFp1TFa`MJTIF{Hee;nKASad~Ul%8a|U0om#&GxQ^Jo=(4_ddmA8gr7#46n$1qKJ%|?$lmC=vA1qc+y8*Y!{Kh&5FF0xB(f( z3pK;dHi+I4`qFTQhy(^?l&M_9YUQeoI1^-MNIFz^m92uDU7*pIX~3N;x2VVL5`6>G zhv?d5g3R#4QBmeS8X9>|?L~Gz=(ch_+}aMVIb_whqAg|K;-dlHI=1)=sZB;0nigew z#yKJXtakAEjl_lXC$sGm^uFmz0BnyDH3FUj7%KpnslIN!5s*2zw;B9$4IiI|hRn!1 zm>2@TKiMr0F%aBtr`xTcY6A*D%Z6fMPsb zJ@~n*a>8DO-6=;F@8T^%8?-C7m}u$0pqaF{^6e`#rH0#FWqkesb3bbJM{~T&$U?j# z1#t=N29zh}cJLAF7&1dE*@cn%j@p@^!#FbAslzzwmBz>H~!TYgoiB9)-;HI`jpu_N~}oT0%=W6wkd`>JXjel z$wVOxc6j*DkC+mYSI;zKf9GGA2r`by>bp-xI%+@Azwz7u?Ij&#?2L;LdIF8@fFokZ Oop8Dpc{wbwDu4iP1)B!| literal 0 HcmV?d00001 diff --git a/packages/next/public/images/vercel.svg b/packages/next/public/images/vercel.svg new file mode 100644 index 0000000..08d0cd1 --- /dev/null +++ b/packages/next/public/images/vercel.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/next/tsconfig.json b/packages/next/tsconfig.json new file mode 100644 index 0000000..2a88d71 --- /dev/null +++ b/packages/next/tsconfig.json @@ -0,0 +1,50 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/components/*": [ + "app/components/*" + ], + "@/lib/*": [ + "app/lib/*" + ], + "@/assets/*": [ + "assets/*" + ] + }, + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ] + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + "dist/types/**/*.ts", + ".next/types/**/*.ts", + "assets/svg/tes.jsx" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/packages/strapi/.dockerignore b/packages/strapi/.dockerignore new file mode 100644 index 0000000..e6fa5c0 --- /dev/null +++ b/packages/strapi/.dockerignore @@ -0,0 +1,8 @@ +.tmp/ +.cache/ +.git/ +build/ +node_modules/ +.env +data/ +backup/ \ No newline at end of file diff --git a/packages/strapi/.gitignore b/packages/strapi/.gitignore new file mode 100644 index 0000000..afa1f1d --- /dev/null +++ b/packages/strapi/.gitignore @@ -0,0 +1,118 @@ +.env* +tunnel.conf + +############################ +# OS X +############################ + +.DS_Store +.AppleDouble +.LSOverride +Icon +.Spotlight-V100 +.Trashes +._* + + +############################ +# Linux +############################ + +*~ + + +############################ +# Windows +############################ + +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ +*.cab +*.msi +*.msm +*.msp + + +############################ +# Packages +############################ + +*.7z +*.csv +*.dat +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip +*.com +*.class +*.dll +*.exe +*.o +*.seed +*.so +*.swo +*.swp +*.swn +*.swm +*.out +*.pid + + +############################ +# Logs and databases +############################ + +.tmp +*.log +*.sql +*.sqlite +*.sqlite3 + + +############################ +# Misc. +############################ + +*# +ssl +.idea +nbproject +public/uploads/* +!public/uploads/.gitkeep + +############################ +# Node.js +############################ + +lib-cov +lcov.info +pids +logs +results +node_modules +.node_history + +############################ +# Tests +############################ + +testApp +coverage + +############################ +# Strapi +############################ + +.env +license.txt +exports +*.cache +dist +build +.strapi-updater.json diff --git a/packages/strapi/.nvmrc b/packages/strapi/.nvmrc new file mode 100644 index 0000000..a77793e --- /dev/null +++ b/packages/strapi/.nvmrc @@ -0,0 +1 @@ +lts/hydrogen diff --git a/packages/strapi/.strapi/client/app.js b/packages/strapi/.strapi/client/app.js new file mode 100644 index 0000000..e0f39d0 --- /dev/null +++ b/packages/strapi/.strapi/client/app.js @@ -0,0 +1,14 @@ +/** + * This file was automatically generated by Strapi. + * Any modifications made will be discarded. + */ +import i18N from "@strapi/plugin-i18n/strapi-admin"; +import usersPermissions from "@strapi/plugin-users-permissions/strapi-admin"; +import { renderAdmin } from "@strapi/strapi/admin"; + +renderAdmin(document.getElementById("strapi"), { + plugins: { + i18n: i18N, + "users-permissions": usersPermissions, + }, +}); diff --git a/packages/strapi/.strapi/client/index.html b/packages/strapi/.strapi/client/index.html new file mode 100644 index 0000000..08d9c27 --- /dev/null +++ b/packages/strapi/.strapi/client/index.html @@ -0,0 +1,62 @@ + + + + + + + + + Strapi Admin + + + +
    +
    + + diff --git a/packages/strapi/Dockerfile b/packages/strapi/Dockerfile new file mode 100644 index 0000000..107ffc9 --- /dev/null +++ b/packages/strapi/Dockerfile @@ -0,0 +1,19 @@ +FROM node:18.16-alpine +# Installing libvips-dev for sharp Compatibility +RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev nasm bash vips-dev +ARG NODE_ENV=development +ENV NODE_ENV=${NODE_ENV} + +WORKDIR /opt/ +COPY package.json yarn.lock ./ +RUN yarn config set network-timeout 600000 -g && yarn install + +WORKDIR /opt/app +COPY . . +ENV PATH /opt/node_modules/.bin:$PATH +RUN chown -R node:node /opt/app +USER node +RUN ["yarn", "build"] +EXPOSE 1337 +# CMD ["yarn", "develop"] +CMD yarn develop \ No newline at end of file diff --git a/packages/strapi/README.md b/packages/strapi/README.md new file mode 100644 index 0000000..34338c4 --- /dev/null +++ b/packages/strapi/README.md @@ -0,0 +1,7 @@ +## dev notes + +### patreon campaign benefit ids + + * ironmouse "Thank you" (for testing): 4760169 + * cj_clippy "Full library access" (for production): 9380584 + * cj_clippy "Your URL displayed on Futureporn.net": 10663202 diff --git a/packages/strapi/backup/Dockerfile.1704607848934 b/packages/strapi/backup/Dockerfile.1704607848934 new file mode 100644 index 0000000..9b69e59 --- /dev/null +++ b/packages/strapi/backup/Dockerfile.1704607848934 @@ -0,0 +1,49 @@ +# FROM node:16-alpine as build +# # Installing libvips-dev for sharp Compatibility +# RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev vips-dev > /dev/null 2>&1 +# ARG NODE_ENV=production +# ENV NODE_ENV=${NODE_ENV} +# WORKDIR /opt/ +# COPY ./package.json ./yarn.lock ./ +# ENV PATH /opt/node_modules/.bin:$PATH +# RUN yarn config set network-timeout 600000 -g && yarn install --production +# WORKDIR /opt/app +# COPY ./ . +# RUN yarn build +# FROM node:16-alpine +# RUN apk add --no-cache vips-dev + +# FROM node:16-alpine +# RUN apk add --no-cache vips-dev +# ARG NODE_ENV=production +# ENV NODE_ENV=${NODE_ENV} +# WORKDIR /opt/app +# COPY --from=build /opt/node_modules ./node_modules +# ENV PATH /opt/node_modules/.bin:$PATH +# COPY --from=build /opt/app ./ +# EXPOSE 5555 +# CMD ["yarn", "start"] + + + + +# # following is from the strapi website +FROM node:18-alpine +# Installing libvips-dev for sharp Compatibility +RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev nasm bash vips-dev git +ARG NODE_ENV=development +ENV NODE_ENV=${NODE_ENV} + +WORKDIR /opt/ +COPY package.json yarn.lock ./ +RUN yarn global add node-gyp +RUN yarn config set network-timeout 600000 -g && yarn install +ENV PATH /opt/node_modules/.bin:$PATH + +WORKDIR /opt/app +COPY . . +RUN chown -R node:node /opt/app +USER node +RUN ["yarn", "build"] +EXPOSE 1337 +CMD ["yarn", "dev"] diff --git a/packages/strapi/chisel.sh b/packages/strapi/chisel.sh new file mode 100644 index 0000000..b612516 --- /dev/null +++ b/packages/strapi/chisel.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +. .env +chisel client --auth="${CHISEL_AUTH}" "${CHISEL_SERVER}" R:8900:localhost:1337 + diff --git a/packages/strapi/config/admin.js b/packages/strapi/config/admin.js new file mode 100644 index 0000000..92f535b --- /dev/null +++ b/packages/strapi/config/admin.js @@ -0,0 +1,13 @@ +module.exports = ({ env }) => ({ + auth: { + secret: env('ADMIN_JWT_SECRET'), + }, + apiToken: { + salt: env('API_TOKEN_SALT'), + }, + transfer: { + token: { + salt: env('TRANSFER_TOKEN_SALT'), + }, + }, +}); diff --git a/packages/strapi/config/api.js b/packages/strapi/config/api.js new file mode 100644 index 0000000..62f8b65 --- /dev/null +++ b/packages/strapi/config/api.js @@ -0,0 +1,7 @@ +module.exports = { + rest: { + defaultLimit: 25, + maxLimit: 100, + withCount: true, + }, +}; diff --git a/packages/strapi/config/database.js b/packages/strapi/config/database.js new file mode 100644 index 0000000..63290e0 --- /dev/null +++ b/packages/strapi/config/database.js @@ -0,0 +1,49 @@ +const path = require('path'); + +module.exports = ({ env }) => { + const client = env('DATABASE_CLIENT', 'postgres'); + + const connections = { + postgres: { + connection: { + connectionString: env('DATABASE_URL'), + host: env('DATABASE_HOST', 'localhost'), + port: env.int('DATABASE_PORT', 5432), + database: env('DATABASE_NAME', 'strapi'), + user: env('DATABASE_USERNAME', 'strapi'), + password: env('DATABASE_PASSWORD', 'strapi'), + ssl: env.bool('DATABASE_SSL', false) && { + key: env('DATABASE_SSL_KEY', undefined), + cert: env('DATABASE_SSL_CERT', undefined), + ca: env('DATABASE_SSL_CA', undefined), + capath: env('DATABASE_SSL_CAPATH', undefined), + cipher: env('DATABASE_SSL_CIPHER', undefined), + rejectUnauthorized: env.bool( + 'DATABASE_SSL_REJECT_UNAUTHORIZED', + true + ), + }, + schema: env('DATABASE_SCHEMA', 'public'), + }, + pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) }, + }, + sqlite: { + connection: { + filename: path.join( + __dirname, + '..', + env('DATABASE_FILENAME', 'data.db') + ), + }, + useNullAsDefault: true, + }, + }; + + return { + connection: { + client, + ...connections[client], + acquireConnectionTimeout: env.int('DATABASE_CONNECTION_TIMEOUT', 60000), + }, + }; +}; diff --git a/packages/strapi/config/middlewares.js b/packages/strapi/config/middlewares.js new file mode 100644 index 0000000..c8b8699 --- /dev/null +++ b/packages/strapi/config/middlewares.js @@ -0,0 +1,26 @@ +module.exports = [ + 'strapi::errors', + { + name: 'strapi::security', + config: { + contentSecurityPolicy: { + useDefaults: true, + directives: { + 'connect-src': ["'self'", 'https:'], + 'img-src': ["'self'", 'data:', 'blob:', 'dl.airtable.com', 'res.cloudinary.com'], + 'media-src': ["'self'", 'data:', 'blob:', 'dl.airtable.com', 'res.cloudinary.com'], + upgradeInsecureRequests: null, + }, + }, + }, + }, + + 'strapi::cors', + 'strapi::poweredBy', + 'strapi::logger', + 'strapi::query', + 'strapi::body', + 'strapi::session', + 'strapi::favicon', + 'strapi::public', +]; diff --git a/packages/strapi/config/plugins.js b/packages/strapi/config/plugins.js new file mode 100644 index 0000000..a47d2a5 --- /dev/null +++ b/packages/strapi/config/plugins.js @@ -0,0 +1,75 @@ +module.exports = ({ + env +}) => ({ + 'fuzzy-search': { + enabled: true, + config: { + contentTypes: [{ + uid: 'api::tag.tag', + modelName: 'tag', + transliterate: false, + queryConstraints: { + where: { + '$and': [ + { + publishedAt: { + '$notNull': true + } + }, + ] + } + }, + fuzzysortOptions: { + characterLimit: 32, + threshold: -600, + limit: 10, + keys: [{ + name: 'name', + weight: 100 + }] + } + }] + } + }, + upload: { + config: { + provider: 'cloudinary', + providerOptions: { + cloud_name: env('CLOUDINARY_NAME'), + api_key: env('CLOUDINARY_KEY'), + api_secret: env('CLOUDINARY_SECRET'), + }, + actionOptions: { + upload: {}, + uploadStream: {}, + delete: {}, + }, + } + }, + email: { + config: { + provider: 'sendgrid', + providerOptions: { + apiKey: env('SENDGRID_API_KEY'), + }, + settings: { + defaultFrom: 'welcome@futureporn.net', + defaultReplyTo: 'cj@futureporn.net', + testAddress: 'grimtech@fastmail.com', + }, + }, + }, + "users-permissions": { + config: { + register: { + allowedFields: [ + "isNamePublic", + "isLinkPublic", + "avatar", + "vanityLink", + "patreonBenefits" + ] + } + } + } +}); \ No newline at end of file diff --git a/packages/strapi/config/server.js b/packages/strapi/config/server.js new file mode 100644 index 0000000..26a385f --- /dev/null +++ b/packages/strapi/config/server.js @@ -0,0 +1,15 @@ +// greets some + +module.exports = ({ env }) => ({ + host: env('HOST', '0.0.0.0'), + port: env.int('PORT', 1337), + proxy: true, + app: { + keys: env.array('APP_KEYS'), + }, + webhooks: { + populateRelations: env.bool('WEBHOOKS_POPULATE_RELATIONS', false) + }, + url: env('STRAPI_URL', 'https://portal.futureporn.net') +}); + diff --git a/packages/strapi/database/daily-backup.sh b/packages/strapi/database/daily-backup.sh new file mode 100644 index 0000000..1ee0aeb --- /dev/null +++ b/packages/strapi/database/daily-backup.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# daily-backup.sh +# useful for the dokku server +# dokku's backup feature is broken atm https://github.com/dokku/dokku-postgres/issues/274 +# backups are exported from dokku:postgres plugin before being sent to b2 + + +filename="$(date +'%Y-%m-%d_%H-%M-%S').psql" + +dokku postgres:export futureporn-db > "${filename}" +b2-linux upload-file futureporn-db-backup "./${filename}" "${filename}" + diff --git a/packages/strapi/database/devDb.sh b/packages/strapi/database/devDb.sh new file mode 100755 index 0000000..a293e46 --- /dev/null +++ b/packages/strapi/database/devDb.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +. .env + +# Check if the containers already exist +pgadmin_exists=$(docker ps -a --filter "name=pgadmin" --format '{{.Names}}') +strapi_postgres_exists=$(docker ps -a --filter "name=strapi-postgres" --format '{{.Names}}') + +# Run strapi-postgres container if it doesn't exist or is not running +if [ -z "$strapi_postgres_exists" ]; then + docker run -d --name strapi-postgres -p 5432:5432 -e POSTGRES_PASSWORD=$POSTGRES_PASSWORD postgres:14.7 + echo "strapi-postgres container created and started." +else + container_status=$(docker inspect -f '{{.State.Status}}' strapi-postgres) + + if [ "$container_status" != "running" ]; then + docker start strapi-postgres + echo "strapi-postgres container started." + else + echo "strapi-postgres container already exists and is running. Skipping creation." + fi +fi diff --git a/packages/strapi/database/migrations/.gitkeep b/packages/strapi/database/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/strapi/database/migrations/2023-08-01-relate-vods-to-vtubers-part2.js b/packages/strapi/database/migrations/2023-08-01-relate-vods-to-vtubers-part2.js new file mode 100644 index 0000000..502ec0c --- /dev/null +++ b/packages/strapi/database/migrations/2023-08-01-relate-vods-to-vtubers-part2.js @@ -0,0 +1,25 @@ +module.exports = { + async up(knex) { + // ... (Create vods_vtuber_links table if not already created) + + // Get vtuber ID for ProjektMelody (assuming it's 1) + const vtuberId = 1; + + // Get all VODs from the database + const vods = await knex.select('*').from('vods'); + + // For each VOD, associate it with the vtuber (vtuber with ID 1) if not already associated + for (const [index, vod] of vods.entries()) { + const existingAssociation = await knex('vods_vtuber_links') + .where({ vtuber_id: vtuberId, vod_id: vod.id }) + .first(); + if (!existingAssociation) { + await knex('vods_vtuber_links').insert({ + vtuber_id: vtuberId, + vod_id: vod.id, + vod_order: index + 1, // Auto-increment the vod_order number + }); + } + } + }, +}; diff --git a/packages/strapi/database/migrations/2023-08-17-reformat-cdnUrl.js b/packages/strapi/database/migrations/2023-08-17-reformat-cdnUrl.js new file mode 100644 index 0000000..c090e12 --- /dev/null +++ b/packages/strapi/database/migrations/2023-08-17-reformat-cdnUrl.js @@ -0,0 +1,18 @@ +module.exports = { + async up(knex) { + + // Get all B2 Files from the database + const files = await knex.select('*').from('b2_files'); + + // For each B2 File, update cdnUrl + // we do this to change + // erroneous https://futureporn-b2.b-cdn.net/futureporn/:key + // to https://futureporn-b2.b-cdn.net/:key + for (const [index, file] of files.entries()) { + const key = file.key; + const cdnUrl = `https://futureporn-b2.b-cdn.net/${key}`; + await knex('b2_files').update({ cdn_url: cdnUrl }).where({ id: file.id }); + } + }, + }; + \ No newline at end of file diff --git a/packages/strapi/database/migrations/2023-08-20-strip-query-string-from-cid.js b/packages/strapi/database/migrations/2023-08-20-strip-query-string-from-cid.js new file mode 100644 index 0000000..0a254f2 --- /dev/null +++ b/packages/strapi/database/migrations/2023-08-20-strip-query-string-from-cid.js @@ -0,0 +1,23 @@ +const stripQueryString = function (text) { + if (!text) return ''; + return text.split(/[?#]/)[0]; +} + +module.exports = { + async up(knex) { + + // Get all vods + const vods = await knex.select('*').from('vods'); + + // For each vod, update videoSrcHash and video240Hash + // we remove any existing ?filename(...) qs from the cid + for (const [index, vod] of vods.entries()) { + const strippedVideoSrcHash = stripQueryString(vod.video_src_hash) + const strippedVideo240Hash = stripQueryString(vod.video_240_hash) + await knex('vods').update({ video_src_hash: strippedVideoSrcHash }).where({ id: vod.id }); + await knex('vods').update({ video_240_hash: strippedVideo240Hash }).where({ id: vod.id }); + } + + }, + }; + \ No newline at end of file diff --git a/packages/strapi/database/migrations/2023-08-30-remove-cloudinary.js b/packages/strapi/database/migrations/2023-08-30-remove-cloudinary.js new file mode 100644 index 0000000..6f1f5a7 --- /dev/null +++ b/packages/strapi/database/migrations/2023-08-30-remove-cloudinary.js @@ -0,0 +1,13 @@ +module.exports = { + async up(knex) { + + const toys = await knex.select('*').from('toys'); + for (const [index, toy] of toys.entries()) { + if (toy.image_2) { + const existingImageFilename = new URL(toy.image_2).pathname.split('/').at(-1) + await knex('toys').update({ image_2: `https://futureporn-b2.b-cdn.net/${existingImageFilename}` }).where({ id: toy.id }); + } + } + }, + }; + \ No newline at end of file diff --git a/packages/strapi/database/migrations/2023-08-30-toy-image-field-simplify.js b/packages/strapi/database/migrations/2023-08-30-toy-image-field-simplify.js new file mode 100644 index 0000000..66ab1de --- /dev/null +++ b/packages/strapi/database/migrations/2023-08-30-toy-image-field-simplify.js @@ -0,0 +1,33 @@ +module.exports = { + async up(knex) { + // Add the `image2` field (column) as a short text field + await knex.schema.table('toys', (table) => { + table.string('image_2', 512); + }); + + // Get all toys + const toys = await knex.select('*').from('toys'); + + // Update the image2 field with the previous image URLs + for (const toy of toys) { + // lookup the file morph which maps toy to (image) file + const imageFileId = (await knex.select('file_id').from('files_related_morphs').where({ related_id: toy.id }))[0].file_id + + // get the image data from the file + const imageUrl = (await knex.select('url').from('files').where({ id: imageFileId }))[0].url + + if (!imageUrl) continue; + + // Copy the values from image to image2 + await knex('toys').update({ image_2: imageUrl }).where({ id: toy.id }); + } + + const hasImageColumn = await knex.schema.hasColumn('toys', 'image'); + if (hasImageColumn) { + // Drop the `image` column + table.dropColumn('image'); + } + + + }, +}; diff --git a/packages/strapi/database/migrations/2023-09-08-change-date-to-string.js b/packages/strapi/database/migrations/2023-09-08-change-date-to-string.js new file mode 100644 index 0000000..3b03cc0 --- /dev/null +++ b/packages/strapi/database/migrations/2023-09-08-change-date-to-string.js @@ -0,0 +1,23 @@ +module.exports = { + async up(knex) { + // Check if the 'date_2' column exists in the 'vods' table + const hasDate2Column = await knex.schema.hasColumn('vods', 'date_2'); + + if (!hasDate2Column) { + // Add the new 'date_2' column as a string if it doesn't exist + await knex.schema.table('vods', (table) => { + table.string('date_2'); + }); + + // Fetch all existing rows from the 'vods' table + const existingVods = await knex.select('id', 'date').from('vods'); + + // Loop through each row and update 'date_2' with the date value + for (const vod of existingVods) { + await knex('vods') + .where({ id: vod.id }) + .update({ date_2: vod.date.toISOString() }); + } + } + }, +}; diff --git a/packages/strapi/database/migrations/2023-09-08-drop-toys-image.js b/packages/strapi/database/migrations/2023-09-08-drop-toys-image.js new file mode 100644 index 0000000..2119714 --- /dev/null +++ b/packages/strapi/database/migrations/2023-09-08-drop-toys-image.js @@ -0,0 +1,11 @@ +module.exports = { + async up(knex) { + const hasColumn = await knex.schema.hasColumn('toys', 'image'); + + if (hasColumn) { + await knex.schema.table('toys', (table) => { + table.dropColumn('image'); + }); + } + } +}; diff --git a/packages/strapi/database/migrations/2023-09-08-drop-vod-videosrc.js b/packages/strapi/database/migrations/2023-09-08-drop-vod-videosrc.js new file mode 100644 index 0000000..8d1f878 --- /dev/null +++ b/packages/strapi/database/migrations/2023-09-08-drop-vod-videosrc.js @@ -0,0 +1,11 @@ +module.exports = { + async up(knex) { + const hasColumn = await knex.schema.hasColumn('vods', 'video_src'); + + if (hasColumn) { + await knex.schema.table('vods', (table) => { + table.dropColumn('video_src'); + }); + } + } +}; diff --git a/packages/strapi/database/migrations/2023-12-24-add-cuid-to-vods.js b/packages/strapi/database/migrations/2023-12-24-add-cuid-to-vods.js new file mode 100644 index 0000000..5590b99 --- /dev/null +++ b/packages/strapi/database/migrations/2023-12-24-add-cuid-to-vods.js @@ -0,0 +1,31 @@ + +const generateCuid = require('../../misc/generateCuid'); + +module.exports = { + async up(knex) { + + console.log(`MIGRATION-- 2023-12-24-add-cuid-to-vods.js`); + + // Check if the 'cuid' column already exists in the 'vods' table + const hasCuidColumn = await knex.schema.hasColumn('vods', 'cuid'); + + if (!hasCuidColumn) { + // Add the 'cuid' column to the 'vods' table + await knex.schema.table('vods', (table) => { + table.string('cuid'); + }); + } + + // Get all vods from the database + const vods = await knex.select('*').from('vods'); + + // For each vod, populate cuid if it's null or undefined + for (const [index, vod] of vods.entries()) { + if (!vod.cuid) { + await knex('vods').update({ cuid: generateCuid() }).where({ id: vod.id }); + } + } + + }, +}; + diff --git a/packages/strapi/database/migrations/2023-12-26-add-cuid-to-streams.js b/packages/strapi/database/migrations/2023-12-26-add-cuid-to-streams.js new file mode 100644 index 0000000..942854b --- /dev/null +++ b/packages/strapi/database/migrations/2023-12-26-add-cuid-to-streams.js @@ -0,0 +1,33 @@ + +const { init } = require('@paralleldrive/cuid2'); + +module.exports = { + async up(knex) { + + console.log(`MIGRATION-- 2023-12-26-add-cuid-to-streams.js`); + + // Check if the 'cuid' column already exists in the 'streams' table + const hasCuidColumn = await knex.schema.hasColumn('streams', 'cuid'); + + if (!hasCuidColumn) { + // Add the 'cuid' column to the 'streams' table + await knex.schema.table('streams', (table) => { + table.string('cuid'); + }); + } + + // Get all streams from the database + const streams = await knex.select('*').from('streams'); + + // For each stream, populate cuid if it's null or undefined + for (const [index, stream] of streams.entries()) { + if (!stream.cuid) { + const length = 10; + const genCuid = init({ length }); + await knex('streams').update({ cuid: genCuid() }).where({ id: stream.id }); + } + } + + }, +}; + diff --git a/packages/strapi/database/migrations/2023-12-27-relate-vods-to-streams.js b/packages/strapi/database/migrations/2023-12-27-relate-vods-to-streams.js new file mode 100644 index 0000000..703275d --- /dev/null +++ b/packages/strapi/database/migrations/2023-12-27-relate-vods-to-streams.js @@ -0,0 +1,35 @@ + +const { sub, add } = require('date-fns'); + + +module.exports = { + async up(knex) { + console.log(`MIGRATION-- 2023-12-27-relate-vods-to-streams.js`); + + // Get all VODs from the database + const vods = await knex.select('*').from('vods'); + + // For each VOD, associate it with the stream with the nearest date (if not already associated) + for (const [index, vod] of vods.entries()) { + const existingAssociation = await knex('vods_stream_links') + .where({ vod_id: vod.id }) + .first(); + + if (!existingAssociation) { + // get nearest stream within +/- 3 hours + const date2 = new Date(vod.date_2); + const startDate = sub(date2, { hours: 3 }) + const endDate = add(date2, { hours: 3 }); + console.log(`vod.id=${vod.id}, vod.date_2=${vod.date_2}, date2=${date2}, startDate=${startDate}, endDate=${endDate}`) + const stream = await knex('streams') + .whereBetween('date', [startDate, endDate]) + + await knex('vods_stream_links').insert({ + stream_id: stream.id, + vod_id: vod.id, + vod_order: 1, + }); + } + } + }, +}; diff --git a/packages/strapi/database/migrations/2023.05.09-video-src-sanity.js.noexec b/packages/strapi/database/migrations/2023.05.09-video-src-sanity.js.noexec new file mode 100644 index 0000000..cda7f9d --- /dev/null +++ b/packages/strapi/database/migrations/2023.05.09-video-src-sanity.js.noexec @@ -0,0 +1,26 @@ + +const fetch = require('node-fetch') + +let problemUrls = [] + +async function checkUrl(url) { + const res = await fetch(url); + if (!res.ok || !res?.headers?.get('x-bz-file-name') || !res?.headers?.get('x-bz-file-id')) problemUrls.push(url) +} + + + +module.exports = { + async up(knex) { + + // Get all VODs from the database + const vods = await knex.select('*').from('vods'); + + // sanity check every B2 URL + for (const vod of vods) { + await checkUrl(vod.video_src) + } + + process.exit(5923423) + }, +}; \ No newline at end of file diff --git a/packages/strapi/database/migrations/2023.05.11T12.32.00.convert-to-video-src-b2.js.noexec b/packages/strapi/database/migrations/2023.05.11T12.32.00.convert-to-video-src-b2.js.noexec new file mode 100644 index 0000000..41d8608 --- /dev/null +++ b/packages/strapi/database/migrations/2023.05.11T12.32.00.convert-to-video-src-b2.js.noexec @@ -0,0 +1,98 @@ + +const fetch = require('node-fetch') + +// greets chatgpt +async function getFileDetailsFromUrl(url) { + const controller = new AbortController(); + const signal = controller.signal; + + const options = { + signal, + }; + + let retries = 10; + + while (retries) { + console.log(`fetching ${url}`); + const timeoutId = setTimeout(() => { + console.log('fetch timed out, aborting...'); + controller.abort(); + }, 5000); + + try { + const res = await fetch(url, options); + + clearTimeout(timeoutId); + + console.log('finished fetch'); + if (!res.ok) throw new Error(`problem while getting file from url with url ${url}`); + if (!res?.headers?.get('x-bz-file-name')) throw new Error(`${url} did not have a x-bz-file-name in the response headers`); + if (!res?.headers?.get('x-bz-file-id')) throw new Error(`${url} did not have a x-bz-file-id in the response headers`); + + return { + key: res.headers.get('x-bz-file-name'), + url: url, + uploadId: res.headers.get('x-bz-file-id'), + }; + } catch (err) { + clearTimeout(timeoutId); + retries--; + + if (retries === 0) { + console.error(`Could not fetch file details from URL: ${url}.`); + throw err; + } + + console.warn(`Retrying fetch (${retries} attempts left)`); + } + } +} + + + + +module.exports = { + async up(knex) { + // You have full access to the Knex.js API with an already initialized connection to the database + + // Get all VODs from the database + const vods = await knex.select('*').from('vods'); + + + // Process each VOD + for (const vod of vods) { + + // courtesy timer + await new Promise((resolve) => setTimeout(resolve, 1000)) + + console.log(vod) + // Get the file details from the VOD's video source URL + if (vod?.video_src) { + try { + const fileDetails = await getFileDetailsFromUrl(vod.video_src); + + // Insert the B2 file into the database + const [file] = await knex('b2_files').insert({ + url: fileDetails.url, + key: fileDetails.key, + upload_id: fileDetails.uploadId, + }).returning('id'); + + console.log(file) + console.log(`attempting to insert vod_id:${vod.id}, b_2_file_id:${file.id} for videoSrcB2`) + + // Link the B2 file to the VOD + await knex('vods_video_src_b_2_links').insert({ + vod_id: vod.id, + b_2_file_id: file.id, + }); + } catch (e) { + console.error(e) + console.log(`there was an error so we are skipping vod ${vod.id}`) + } + } else { + console.log(`${vod.id} has no video_src. skipping.`) + } + } + }, +}; \ No newline at end of file diff --git a/packages/strapi/database/migrations/2023.05.14T00.42.00.000Z.migrate-tags-to-tag-vod-relations.js b/packages/strapi/database/migrations/2023.05.14T00.42.00.000Z.migrate-tags-to-tag-vod-relations.js new file mode 100644 index 0000000..7c3e24a --- /dev/null +++ b/packages/strapi/database/migrations/2023.05.14T00.42.00.000Z.migrate-tags-to-tag-vod-relations.js @@ -0,0 +1,43 @@ + +// up until now, tags have been attached directly to each vod object. +// now, tags are not attached to vods. +// instead, tag-vod-relations are used to associate a tag with a vod + +// what we need to do in this migration is +// * create a new tag-vod-relation for each tag in each vod +// * delete tags field in vods + +module.exports = { + async up(knex) { + + console.log('2023.05.14 - migrate tags to tag_vod_relations') + + // get all tags_vods_links + // for each, create a tag-vod-relation + const tagsVodsLinks = await knex.select('*').from('tags_vods_links') + + for (const tvl of tagsVodsLinks) { + // Create a tag-vod-relation entry for each tag + const tvr = await knex('tag_vod_relations') + .insert({ + created_at: new Date(), + updated_at: new Date(), + creator_id: 1 + }) + .returning( + ['id'] + ) + + await knex('tag_vod_relations_tag_links').insert({ + tag_vod_relation_id: tvr[0].id, + tag_id: tvl.tag_id + }) + + await knex('tag_vod_relations_vod_links').insert({ + tag_vod_relation_id: tvr[0].id, + vod_id: tvl.vod_id + }) + } + + }, +}; \ No newline at end of file diff --git a/packages/strapi/database/migrations/2023.05.15T02.44.00.000Z.drop-vod-tags.js b/packages/strapi/database/migrations/2023.05.15T02.44.00.000Z.drop-vod-tags.js new file mode 100644 index 0000000..15fc208 --- /dev/null +++ b/packages/strapi/database/migrations/2023.05.15T02.44.00.000Z.drop-vod-tags.js @@ -0,0 +1,12 @@ + +// previously, we tagged vods directly on the vod content-type +// now, we use tag-vod-relation to relate tags to vods. +// thus, we want to get rid of vod.tags +// and also tag.vods + +module.exports = { + async up(knex) { + console.log('2023.05.15 - drop tags_vods_links') + await knex.schema.dropTable('tags_vods_links') + } +} \ No newline at end of file diff --git a/packages/strapi/database/migrations/2023.05.25-gimme-the-tags.js.noexec b/packages/strapi/database/migrations/2023.05.25-gimme-the-tags.js.noexec new file mode 100644 index 0000000..65af409 --- /dev/null +++ b/packages/strapi/database/migrations/2023.05.25-gimme-the-tags.js.noexec @@ -0,0 +1,110 @@ + +// const fetch = require('node-fetch') + +// // greets chatgpt +// async function getFileDetailsFromUrl(url) { +// const controller = new AbortController(); +// const signal = controller.signal; + +// const options = { +// signal, +// }; + +// let retries = 10; + +// while (retries) { +// console.log(`fetching ${url}`); +// const timeoutId = setTimeout(() => { +// console.log('fetch timed out, aborting...'); +// controller.abort(); +// }, 5000); + +// try { +// const res = await fetch(url, options); + +// clearTimeout(timeoutId); + +// console.log('finished fetch'); +// if (!res.ok) throw new Error(`problem while getting file from url with url ${url}`); +// if (!res?.headers?.get('x-bz-file-name')) throw new Error(`${url} did not have a x-bz-file-name in the response headers`); +// if (!res?.headers?.get('x-bz-file-id')) throw new Error(`${url} did not have a x-bz-file-id in the response headers`); + +// return { +// key: res.headers.get('x-bz-file-name'), +// url: url, +// uploadId: res.headers.get('x-bz-file-id'), +// }; +// } catch (err) { +// clearTimeout(timeoutId); +// retries--; + +// if (retries === 0) { +// console.error(`Could not fetch file details from URL: ${url}.`); +// throw err; +// } + +// console.warn(`Retrying fetch (${retries} attempts left)`); +// } +// } +// } + + + + +module.exports = { + async up(knex) { + // You have full access to the Knex.js API with an already initialized connection to the database + + + // we iterate through the local, non-strapi backup db first. + // get list of all tags + // for each tag + // * get list of related vods + // * create relation in Strapi + // * + + + + // Get all VODs from the database + const vods = await knex.select('*').from('vods'); + + + // Process each VOD + for (const vod of vods) { + + // courtesy timer + await new Promise((resolve) => setTimeout(resolve, 10)) + + // @todo + + console.log(vod) + // Get the file details from the VOD's video source URL + if (vod?.video_src) { + try { + const fileDetails = await getFileDetailsFromUrl(vod.video_src); + + // Insert the B2 file into the database + const [file] = await knex('b2_files').insert({ + url: fileDetails.url, + key: fileDetails.key, + upload_id: fileDetails.uploadId, + }).returning('id'); + + console.log(file) + console.log(`attempting to insert vod_id:${vod.id}, b_2_file_id:${file.id} for videoSrcB2`) + + // Link the B2 file to the VOD + await knex('vods_video_src_b_2_links').insert({ + vod_id: vod.id, + b_2_file_id: file.id, + }); + } catch (e) { + console.error(e) + console.log(`there was an error so we are skipping vod ${vod.id}`) + } + } else { + console.log(`${vod.id} has no video_src. skipping.`) + } + } + }, +}; \ No newline at end of file diff --git a/packages/strapi/database/migrations/2023.05.25T20.44.00.000Z.get-the-og-tags.js b/packages/strapi/database/migrations/2023.05.25T20.44.00.000Z.get-the-og-tags.js new file mode 100644 index 0000000..a211ebb --- /dev/null +++ b/packages/strapi/database/migrations/2023.05.25T20.44.00.000Z.get-the-og-tags.js @@ -0,0 +1,124 @@ +'use strict' + + +require('dotenv').config() + +const { Client } = require('pg') +const fetch = require('node-fetch') +const _ = require('lodash'); +const ogVods = require('../og-tags.json') + + +// const slugify = require('slugify') + + +// function slugifyString (str) { +// return slugify(str, { +// replacement: '-', // replace spaces with replacement character, defaults to `-` +// remove: undefined, // remove characters that match regex, defaults to `undefined` +// lower: true, // convert to lower case, defaults to `false` +// strict: true, // strip special characters except replacement, defaults to `false` +// locale: 'en', // language code of the locale to use +// trim: true // trim leading and trailing replacement chars, defaults to `true` +// }) +// } + + +async function associateTagWithVodsInStrapi (tagId, vodsIds) { + const res = await fetch(`${process.env.STRAPI_URL}/api/tags/${tagId}`, { + method: 'PUT', + headers: { + 'authorization': `Bearer ${process.env.STRAPI_API_KEY}` + }, + data: { + vods: [vodsIds] + } + }) + const json = await res.json() + + + if (!res.ok) throw new Error(JSON.stringify(json)) +} + + + +async function associateVodWithTagsInStrapi (knex, vodId, tagsIds) { + console.log(`updating vodId:${vodId} with tagsIds:${tagsIds}`) + for (const tagId of tagsIds) { + // see if it exists already + const rows = await knex.select('*').from('tags_vods_links').where({ + 'vod_id': vodId, + 'tag_id': tagId + }) + if (rows.length === 0) { + await knex('tags_vods_links').insert({ + vod_id: vodId, + tag_id: tagId + }); + } + } +} + +async function getStrapiVodByAnnounceUrl (knex, announceUrl) { + const rows = await knex.select('*').from('vods').where('announce_url', announceUrl) + return (rows[0]) +} + + + + +async function getStrapiTagByName (knex, tag) { + const rows = await knex.select('*').from('tags').where({ 'name': tag }) + return rows[0] +} + + + + + +module.exports = { + async up(knex) { + // You have full access to the Knex.js API with an already initialized connection to the database + + for (const vod of ogVods) { + // get matching vod in strapi + console.log(vod) + if (vod.announceUrl) { + const strapiVod = await getStrapiVodByAnnounceUrl(knex, vod.announceUrl) + + if (strapiVod) { + // we've got a matching vod + + if (vod.tags) { + console.log(`source vod has tags: ${vod.tags}`) + + let strapiTagsIds = [] + + // for each tag, get the matching strapi tag ID + for (const tag of vod.tags) { + // lookup the strapi tag id + const strapiTag = await getStrapiTagByName(knex, tag) + if (!!strapiTag) { + strapiTagsIds.push(strapiTag.id) + } + } + + console.log(`we are adding the following strapiTagsIds to vod ID ${strapiVod.id}: ${strapiTagsIds}`) + + // create relations between matching vod and the tags + await associateVodWithTagsInStrapi(knex, strapiVod.id, strapiTagsIds) + + } + } + } + } + + // Get all VODs from the database + const vods = await knex.select('*').from('vods'); + + // Process each VOD + for (const vod of vods) { + + } + } +} diff --git a/packages/strapi/database/migrations/2023.07.17.relate-vods-to-vtubers.js b/packages/strapi/database/migrations/2023.07.17.relate-vods-to-vtubers.js new file mode 100644 index 0000000..5f035a1 --- /dev/null +++ b/packages/strapi/database/migrations/2023.07.17.relate-vods-to-vtubers.js @@ -0,0 +1,70 @@ +module.exports = { + async up(knex) { + + console.log('Create vtubers table') + await knex.schema.createTable('vtubers', (table) => { + table.increments('id').primary(); + table.string('displayName').notNullable(); + table.string('chaturbate'); + table.string('twitter'); + table.string('patreon'); + table.string('twitch'); + table.string('tiktok'); + table.string('onlyfans'); + table.string('youtube'); + table.string('linktree'); + table.string('carrd'); + table.string('fansly'); + table.string('pornhub'); + table.string('discord'); + table.string('reddit'); + table.string('throne'); + table.string('instagram'); + table.string('facebook'); + table.string('merch'); + table.string('slug').notNullable(); + table.text('description1').notNullable(); + table.text('description2'); + table.string('image').notNullable(); + }); + + console.log('Create vods_vtuber_links table') + await knex.schema.createTable('vods_vtuber_links', (table) => { + table.increments('id').primary(); + table.integer('vod_id').unsigned().references('vods.id'); + table.integer('vtuber_id').unsigned().references('vtubers.id'); + table.integer('vod_order').notNullable(); + }); + + + console.log('Create a vtuber entry for ProjektMelody') + const projektMelody = { + displayName: 'ProjektMelody', + slug: 'projektmelody', // You can customize the slug based on your preference + description1: 'Description for ProjektMelody', // Add your vtuber's description here + image: 'http://futureporn-b2.b-cdn.net/futureporn/projekt-melody.jpg', // Replace with the image filename for ProjektMelody + }; + + console.log('Get all VODs from the database') + const vods = await knex.select('*').from('vods'); + + console.log('get projektmelody id') + // const [projektMelodyId] = await knex('vtubers').insert(projektMelody); + const projektMelodyId = 1 + + console.log(`projektmelodyId is : ${projektMelodyId}`) + + console.log(`For each VOD, associate ProjektMelody vtuber.`) + for (const [index, vod] of vods.entries()) { + console.log(`Check if vtuber_id exists in the vtubers table`) + const vtuber = await knex('vtubers').where('id', projektMelodyId).first(); + if (vtuber) { + await knex('vods_vtuber_links').insert({ + vtuber_id: projektMelodyId, + vod_id: vod.id, + vod_order: index + 1, // Auto-increment the vod_order number + }); + } + } + }, +}; diff --git a/packages/strapi/database/migrations/2023.07.31.add-b2-file-cdnUrl.js b/packages/strapi/database/migrations/2023.07.31.add-b2-file-cdnUrl.js new file mode 100644 index 0000000..308f159 --- /dev/null +++ b/packages/strapi/database/migrations/2023.07.31.add-b2-file-cdnUrl.js @@ -0,0 +1,18 @@ +module.exports = { + async up(knex) { + // Add the 'cdn_url' column to the 'b2_files' table + await knex.schema.table('b2_files', (table) => { + table.string('cdn_url'); // Change the data type if needed (e.g., text, varchar, etc.) + }); + + // Get all B2 Files from the database + const files = await knex.select('*').from('b2_files'); + + // For each B2 File, create cdnUrl + for (const [index, file] of files.entries()) { + const key = file.key; + const cdnUrl = `https://futureporn-b2.b-cdn.net/futureporn/${key}`; + await knex('b2_files').update({ cdn_url: cdnUrl }).where({ id: file.id }); + } + }, +}; diff --git a/packages/strapi/database/migrations/2024-01-08-add-streams.js.noexec b/packages/strapi/database/migrations/2024-01-08-add-streams.js.noexec new file mode 100644 index 0000000..d354107 --- /dev/null +++ b/packages/strapi/database/migrations/2024-01-08-add-streams.js.noexec @@ -0,0 +1,30 @@ +module.exports = { + async up(knex) { + + await knex.schema.createTable('streams', (table) => { + table.increments('id').primary(); + table.string('date_str').notNullable(); + table.string('vods'); + table.string('vtuber'); + table.string('tweet'); + table.string('date'); + table.string('cuid'); + }); + + + // Add the 'cdn_url' column to the 'b2_files' table + await knex.schema.table('b2_files', (table) => { + table.string('cdn_url'); // Change the data type if needed (e.g., text, varchar, etc.) + }); + + // Get all B2 Files from the database + const files = await knex.select('*').from('b2_files'); + + // For each B2 File, create cdnUrl + for (const [index, file] of files.entries()) { + const key = file.key; + const cdnUrl = `https://futureporn-b2.b-cdn.net/futureporn/${key}`; + await knex('b2_files').update({ cdn_url: cdnUrl }).where({ id: file.id }); + } + }, +}; diff --git a/packages/strapi/database/migrations/2024-01-14-add-date2-to-streams.js b/packages/strapi/database/migrations/2024-01-14-add-date2-to-streams.js new file mode 100644 index 0000000..5cbd5cf --- /dev/null +++ b/packages/strapi/database/migrations/2024-01-14-add-date2-to-streams.js @@ -0,0 +1,29 @@ + +module.exports = { + async up(knex) { + + console.log(`MIGRATION-- 2024-01-14-add-date2-to-streams.js`); + + // Check if the 'date_2' column already exists in the 'streams' table + const hasColumn = await knex.schema.hasColumn('streams', 'date_2'); + + if (!hasColumn) { + console.log(`Adding the 'date_2' column to the 'streams' table`); + await knex.schema.table('streams', (table) => { + table.string('date_2'); + }); + } + + // Get all streams from the database + const streams = await knex.select('*').from('streams'); + + // For each stream, populate date_2 if it's null or undefined + for (const [index, stream] of streams.entries()) { + if (stream.date_2 === null && stream.date_str !== null) { + const result = await knex('streams').update({ date_2: stream.date_str }).where({ id: stream.id }); + } + } + + }, +}; + diff --git a/packages/strapi/database/migrations/2024-01-15-add-platform-to-streams.js b/packages/strapi/database/migrations/2024-01-15-add-platform-to-streams.js new file mode 100644 index 0000000..a1632e9 --- /dev/null +++ b/packages/strapi/database/migrations/2024-01-15-add-platform-to-streams.js @@ -0,0 +1,49 @@ + +module.exports = { + async up(knex) { + + console.log(`MIGRATION-- 2024-01-15-add-platform-streams.js`); + + // Check if the 'platform' column already exists in the 'streams' table + const hasColumn = await knex.schema.hasColumn('streams', 'platform'); + + if (!hasColumn) { + console.log(`Adding the 'platform' column to the 'streams' table`); + await knex.schema.table('streams', (table) => { + table.string('platform'); + }); + } + + // Get all streams from the database + const streams = await knex.select('*').from('streams'); + + // For each stream, populate platform based on the related tweet data + for (const [index, stream] of streams.entries()) { + + const tweetLink = await knex('streams_tweet_links') + .where({ stream_id: stream.id }) + .first(); + + if (tweetLink) { + console.log(tweetLink); + + const tweet = await knex('tweets') + .where({ id: tweetLink.tweet_id }) + .first(); + + console.log(tweet); + + if (!!tweet) { + console.log(`stream ${stream.id} tweet tweet.is_chaturbate_invite=${tweet.is_chaturbate_invite}, tweet.is_fansly_invite=${tweet.is_fansly_invite}`); + await knex('streams').update({ + is_chaturbate_stream: !!tweet.is_chaturbate_invite, + is_fansly_stream: !!tweet.is_fansly_invite + }).where({ id: stream.id }); + } + } + + } + + }, +}; + diff --git a/packages/strapi/database/og-tags.json b/packages/strapi/database/og-tags.json new file mode 100644 index 0000000..7d838af --- /dev/null +++ b/packages/strapi/database/og-tags.json @@ -0,0 +1,1009 @@ +[{ + "tags": null, + "date": "2020-02-09T18:14:25.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1226570058073362438" +}, { + "tags": null, + "date": "2020-02-10T02:15:55.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1226691233659183104" +}, { + "tags": null, + "date": "2020-02-12T01:11:48.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1227399871734468610" +}, { + "tags": null, + "date": "2020-02-12T17:14:12.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1227642067733618691" +}, { + "tags": null, + "date": "2020-02-13T02:09:11.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1227776703415693312" +}, { + "tags": null, + "date": "2020-02-15T02:13:56.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1228502672246005760" +}, { + "tags": null, + "date": "2020-02-18T21:17:28.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1229877618067025923" +}, { + "tags": null, + "date": "2020-02-20T01:06:47.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1230297714719674368" +}, { + "tags": null, + "date": "2020-02-22T02:13:38.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1231039313300901889" +}, { + "tags": null, + "date": "2023-02-19T00:15:09.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1627099434508075008" +}, { + "tags": null, + "date": "2020-02-09T01:40:10.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1226319848411717632" +}, { + "tags": null, + "date": "2020-02-21T16:25:47.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1230891377422192641" +}, { + "tags": null, + "date": "2020-03-03T21:06:17.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1234948231311876096" +}, { + "tags": null, + "date": "2020-03-05T01:18:07.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1235373996906078208" +}, { + "tags": null, + "date": "2020-03-07T02:40:52.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1236119594995245057" +}, { + "tags": null, + "date": "2020-03-12T00:01:06.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1237891327922016258" +}, { + "tags": null, + "date": "2020-03-15T20:22:40.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1239285911201988609" +}, { + "tags": null, + "date": "2020-03-17T19:55:46.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1240003917355921412" +}, { + "tags": null, + "date": "2020-03-22T00:11:12.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1241517748276154371" +}, { + "tags": null, + "date": "2020-03-26T00:14:56.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1242968240894234627" +}, { + "tags": null, + "date": "2020-03-01T21:06:17.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1234223457723961344" +}, { + "tags": ["anal", "deep throat", "vore", "stuck in wall", "choking", "puddle", "squirting"], + "date": "2020-02-08T16:12:42.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1226177041109786625" +}, { + "tags": null, + "date": "2020-02-28T16:38:43.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1233431346112061441" +}, { + "tags": null, + "date": "2020-04-14T19:57:03.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1250151101174816769" +}, { + "tags": null, + "date": "2020-04-24T00:55:16.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1253487639430410240" +}, { + "tags": null, + "date": "2020-04-25T23:05:42.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1254184839684726786" +}, { + "tags": null, + "date": "2020-04-26T22:54:50.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1254544493849739264" +}, { + "tags": null, + "date": "2020-05-09T21:59:16.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1259241552016216065" +}, { + "tags": null, + "date": "2020-05-12T19:01:53.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1260284077011238912" +}, { + "tags": null, + "date": "2020-06-27T00:06:39.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1276668227767144452" +}, { + "tags": null, + "date": "2020-02-17T01:05:40.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1229210268447715328" +}, { + "tags": null, + "date": "2020-04-05T22:55:16.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1246934456927215617" +}, { + "tags": null, + "date": "2020-05-05T19:00:56.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1257747123682119680" +}, { + "tags": null, + "date": "2020-05-28T19:57:30.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1266096281002356736" +}, { + "tags": null, + "date": "2020-05-29T23:02:07.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1266505128426815488" +}, { + "tags": null, + "date": "2020-06-01T00:13:53.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1267247965464297473" +}, { + "tags": null, + "date": "2020-06-06T00:02:11.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1269056957295583240" +}, { + "tags": null, + "date": "2020-06-16T18:55:04.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1272965936685953024" +}, { + "tags": null, + "date": "2020-06-18T23:37:57.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1273761903664336897" +}, { + "tags": null, + "date": "2020-06-21T00:09:09.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1274494531312984064" +}, { + "tags": null, + "date": "2020-06-23T20:00:12.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1275519042955153409" +}, { + "tags": null, + "date": "2020-02-27T01:10:55.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1232835470712803328" +}, { + "tags": null, + "date": "2020-05-23T00:02:55.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1263983714893279233" +}, { + "tags": null, + "date": "2020-07-15T00:25:49.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1283196031648358400" +}, { + "tags": null, + "date": "2020-07-18T01:14:51.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1284295535751499776" +}, { + "tags": null, + "date": "2020-07-25T00:00:05.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1286813436119220225" +}, { + "tags": null, + "date": "2020-07-29T20:03:40.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1288565878502699008" +}, { + "tags": null, + "date": "2020-08-01T00:00:00.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1289350128436838400" +}, { + "tags": null, + "date": "2020-08-05T19:52:40.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1291099825396748292" +}, { + "tags": null, + "date": "2020-08-14T22:21:59.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1294398894466629634" +}, { + "tags": null, + "date": "2020-08-19T21:00:11.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1296190247957659649" +}, { + "tags": null, + "date": "2020-03-14T01:38:59.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1238640737668997120" +}, { + "tags": null, + "date": "2020-07-10T23:59:34.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1281739875319009280" +}, { + "tags": null, + "date": "2020-09-11T18:02:31.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1304480454242861056" +}, { + "tags": null, + "date": "2020-09-13T23:00:49.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1305280300130406402" +}, { + "tags": null, + "date": "2020-09-18T22:55:13.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1307090831011205120" +}, { + "tags": null, + "date": "2020-09-28T20:14:36.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1310674290744995843" +}, { + "tags": null, + "date": "2020-10-02T23:48:02.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1312177554824990720" +}, { + "tags": null, + "date": "2020-10-06T22:59:13.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1313614818713231360" +}, { + "tags": null, + "date": "2020-10-09T18:01:05.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1314626953803161601" +}, { + "tags": null, + "date": "2020-04-02T00:11:39.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1245504130753404931" +}, { + "tags": null, + "date": "2020-08-23T22:59:21.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1297669785883484160" +}, { + "tags": null, + "date": "2020-09-20T20:00:12.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1307771561651441665" +}, { + "tags": null, + "date": "2020-10-31T23:03:57.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1322675709379334146" +}, { + "tags": null, + "date": "2020-11-18T21:28:43.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1329174724790915075" +}, { + "tags": null, + "date": "2020-11-23T23:56:04.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1331023742693232646" +}, { + "tags": null, + "date": "2020-11-26T22:03:19.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1332082533526360066" +}, { + "tags": null, + "date": "2020-12-04T21:18:42.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1334970406025965571" +}, { + "tags": null, + "date": "2020-12-07T23:56:41.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1336097330483916800" +}, { + "tags": null, + "date": "2020-04-09T00:06:24.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1248039525215080449" +}, { + "tags": null, + "date": "2020-10-18T18:59:38.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1317903180148166657" +}, { + "tags": null, + "date": "2020-10-29T19:58:10.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1321904176637595648" +}, { + "tags": null, + "date": "2021-01-05T23:07:36.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1346594227233427459" +}, { + "tags": null, + "date": "2021-01-08T23:03:45.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1347680421920862209" +}, { + "tags": null, + "date": "2021-01-12T23:03:22.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1349129873068068873" +}, { + "tags": null, + "date": "2021-01-16T01:01:22.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1350246732391706624" +}, { + "tags": null, + "date": "2021-01-22T23:02:24.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1352753509045776384" +}, { + "tags": null, + "date": "2021-01-26T23:02:21.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1354203048491544581" +}, { + "tags": null, + "date": "2021-02-03T23:00:44.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1357101745961328645" +}, { + "tags": null, + "date": "2020-12-21T21:01:24.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1341126649329410050" +}, { + "tags": null, + "date": "2020-12-28T23:20:13.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1343698295563153408" +}, { + "tags": null, + "date": "2021-02-14T21:13:33.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1361061038620364812" +}, { + "tags": null, + "date": "2021-02-18T00:01:18.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1362190417631076352" +}, { + "tags": null, + "date": "2021-02-20T00:59:31.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1362929843759943681" +}, { + "tags": null, + "date": "2021-02-24T23:01:00.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1364711958105194498" +}, { + "tags": null, + "date": "2021-03-02T23:07:27.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1366887909241012233" +}, { + "tags": null, + "date": "2021-03-12T22:53:59.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1370508398081540098" +}, { + "tags": null, + "date": "2021-03-13T20:58:44.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1370841782506901516" +}, { + "tags": null, + "date": "2021-03-20T21:54:09.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1373392441257103368" +}, { + "tags": null, + "date": "2021-03-22T22:05:15.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1374120011854397442" +}, { + "tags": null, + "date": "2021-02-10T23:26:55.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1359645050792206337" +}, { + "tags": null, + "date": "2021-02-25T00:00:00.000Z", + "announceUrl": null +}, { + "tags": null, + "date": "2021-04-03T01:26:21.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1378156887884005378" +}, { + "tags": null, + "date": "2021-04-03T19:55:31.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1378436020081950726" +}, { + "tags": null, + "date": "2021-04-12T23:11:23.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1381746799506030593" +}, { + "tags": null, + "date": "2021-04-24T17:31:05.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1386009816443805699" +}, { + "tags": null, + "date": "2021-05-01T16:59:01.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1388538459816595463" +}, { + "tags": null, + "date": "2021-05-14T15:53:36.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1393233038298001411" +}, { + "tags": null, + "date": "2021-05-21T22:59:03.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1395876824865988611" +}, { + "tags": null, + "date": "2021-05-25T22:04:21.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1397312608454258690" +}, { + "tags": null, + "date": "2021-06-05T22:02:00.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1401298283499298817" +}, { + "tags": null, + "date": "2021-06-12T22:56:31.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1403848719695482888" +}, { + "tags": null, + "date": "2022-01-09T23:32:49.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1480321695315640328" +}, { + "tags": null, + "date": "2021-06-26T20:08:24.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1408879841315860486" +}, { + "tags": null, + "date": "2021-07-03T22:04:50.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1411445858244169728" +}, { + "tags": null, + "date": "2021-07-07T19:04:50.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1412850109608566786" +}, { + "tags": null, + "date": "2021-07-13T19:52:08.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1415036340580884482" +}, { + "tags": null, + "date": "2021-07-17T20:05:10.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1416489172311887876" +}, { + "tags": null, + "date": "2021-08-04T23:11:38.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1423059078381719564" +}, { + "tags": null, + "date": "2021-08-08T20:26:41.000Z", + "announceUrl": "http://twitter.com/ProjektMelody/status/1424467119006261249" +}, { + "tags": null, + "date": "2021-08-12T19:57:20.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1425909284940955648" +}, { + "tags": null, + "date": "2021-08-17T23:16:50.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1427771428833738755" +}, { + "tags": null, + "date": "2021-08-21T20:13:36.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1429174868935856133" +}, { + "tags": null, + "date": "2022-05-13T19:19:42.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1525194090299670528" +}, { + "tags": null, + "date": "2021-09-09T23:01:37.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1436102522201116673" +}, { + "tags": null, + "date": "2021-09-12T22:03:47.000Z", + "announceUrl": "http://twitter.com/ProjektMelody/status/1437175131223302146" +}, { + "tags": null, + "date": "2021-09-19T19:47:34.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1439677566739259395" +}, { + "tags": null, + "date": "2021-09-21T21:56:01.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1440434669305012228" +}, { + "tags": null, + "date": "2021-09-29T18:55:41.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1443288390007508995" +}, { + "tags": null, + "date": "2021-10-09T20:09:55.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1446930950332506117" +}, { + "tags": null, + "date": "2021-10-11T22:06:20.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1447685023059128320" +}, { + "tags": null, + "date": "2021-10-23T20:18:46.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1452006607626412038" +}, { + "tags": null, + "date": "2021-11-04T22:03:43.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1456381673235492878" +}, { + "tags": null, + "date": "2023-04-04T23:43:09.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1643398832485482497" +}, { + "tags": null, + "date": "2022-09-07T23:13:00.000Z", + "announceUrl": null +}, { + "tags": null, + "date": "2021-11-12T00:08:21.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1458949749986443268" +}, { + "tags": null, + "date": "2021-11-19T20:01:27.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1461786722337972235" +}, { + "tags": null, + "date": "2021-12-06T20:10:31.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1467949594252398599" +}, { + "tags": null, + "date": "2021-12-12T23:12:01.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1470169597546479617" +}, { + "tags": null, + "date": "2021-12-15T23:04:14.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1471254803451887625" +}, { + "tags": null, + "date": "2021-12-17T23:05:54.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1471979997426589699" +}, { + "tags": null, + "date": "2021-12-25T22:34:35.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1474871219610603526" +}, { + "tags": null, + "date": "2021-12-30T23:17:45.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1476694022408261640" +}, { + "tags": null, + "date": "2022-01-03T23:10:33.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1478141764741611527" +}, { + "tags": null, + "date": "2022-01-13T00:03:21.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1481416541493153795" +}, { + "tags": null, + "date": "2022-01-22T22:59:59.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1485024472105164805" +}, { + "tags": null, + "date": "2022-01-26T01:57:25.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1486156290585882626" +}, { + "tags": null, + "date": "2022-02-02T21:48:11.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1488992672022945793" +}, { + "tags": null, + "date": "2022-02-16T23:00:32.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1494084309958987782" +}, { + "tags": null, + "date": "2022-02-18T20:01:16.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1494763968535179274" +}, { + "tags": null, + "date": "2022-03-10T23:02:20.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1502057294246424600" +}, { + "tags": null, + "date": "2022-03-18T01:35:11.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1504632475804446721" +}, { + "tags": null, + "date": "2022-04-06T00:02:17.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1511494467097870340" +}, { + "tags": null, + "date": "2022-04-10T00:12:41.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1512946635655065602" +}, { + "tags": null, + "date": "2022-04-16T23:09:42.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1515467499642531845" +}, { + "tags": null, + "date": "2022-04-22T23:14:35.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1517643056228487168" +}, { + "tags": null, + "date": "2022-05-06T22:59:23.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1522712662349656064" +}, { + "tags": null, + "date": "2022-05-08T22:51:40.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1523435492657664000" +}, { + "tags": null, + "date": "2022-05-19T00:44:58.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1527087885693878275" +}, { + "tags": null, + "date": "2022-05-25T02:06:07.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1529282633762492417" +}, { + "tags": null, + "date": "2022-06-05T23:30:37.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1533592158179139585" +}, { + "tags": null, + "date": "2022-06-09T21:10:03.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1535006331119222784" +}, { + "tags": null, + "date": "2022-06-22T23:46:31.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1539756753147117568" +}, { + "tags": null, + "date": "2022-07-19T03:48:05.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1549239628418490368" +}, { + "tags": null, + "date": "2022-08-03T23:32:24.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1554973488871530501" +}, { + "tags": null, + "date": "2022-08-05T23:15:33.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1555694026376060966" +}, { + "tags": null, + "date": "2022-08-11T20:31:00.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1557827065101127680#m" +}, { + "tags": null, + "date": "2022-08-23T23:28:21.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1562220295683014657" +}, { + "tags": null, + "date": "2022-09-10T21:03:36.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1568706781806821377" +}, { + "tags": null, + "date": "2022-06-18T23:07:00.000Z", + "announceUrl": null +}, { + "tags": null, + "date": "2022-09-13T23:26:31.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1569829911057031170" +}, { + "tags": null, + "date": "2022-09-21T23:38:26.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1572732011529506817" +}, { + "tags": null, + "date": "2022-10-02T02:05:22.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1576392868201365504" +}, { + "tags": null, + "date": "2022-10-04T23:15:33.000Z", + "announceUrl": "http://twitter.com/ProjektMelody/status/1577437242716741632" +}, { + "tags": null, + "date": "2022-10-13T02:22:45.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1580383510908399616" +}, { + "tags": null, + "date": "2022-10-15T00:31:27.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1581080275546537984" +}, { + "tags": ["squirting", "toy", "lovense-lush", "fantasy-for-her-suction", "crave-vesper-necklace", "womanizer-duo", "twitching", "licking", "unboxing", "suction", "review", "orgasm", "tongue", "vibrator", "cum-eating", "pussy-milking", "torture", "rope", "restraint", "bondage", "chains", "dildo", "deep-breathing"], + "date": "2022-10-20T23:21:15.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1583236936445353985" +}, { + "tags": ["jumpsuit", "3d", "new-outfit", "pole-dancing", "lovense-lush", "lovense-hyfy", "blowjob", "bottomless", "ass", "dancing", "pool", "precum", "pussy"], + "date": "2022-11-04T20:29:53.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1588629627907473409" +}, { + "tags": ["video-game", "hentai", "eroge", "daraku-gear", "mobile-game", "buffering", "sponsored", "lovense-lush", "storytime", "hentai", "games", "gacha", "waifu", "womb-tattoo", "bondage", "mind-break", "squirting", "nipple-clamps", "handcuffs", "suction-cups", "prisoner", "slave", "big-brother", "master", "hitachi-magic-wand", "double-penetration", "confusion", "cock-block", "crying", "edging", "cum", "pasties", "robocock", "milking", "hose", "self-censorship", "threesome", "blowjob", "spanking", "cucking", "feet", "sex-training"], + "date": "2022-12-14T00:01:45.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1602816075304935425" +}, { + "tags": ["japan", "monokini", "outfit", "game-of-thrones", "gundam", "growling", "grinding", "flooring", "pool", "lovense-lush", "heavy-breathing", "moaning", "naked", "edging", "womb-tattoo", "pelvic-thrusting", "missionary", "fingering", "cum-drunk", "laughing", "c2c", "self-censorship", "sex-ed", "sex-stories", "glory-hole", "texting", "ass", "2-cooms", "orgasm", "selfie-cam", "non-euclidian", "silly-dancing"], + "date": "2022-12-16T00:30:34.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1603548101561696261" +}, { + "tags": ["silly-music", "guest-dj", "lovense-lush", "3d", "original-outfit", "step-brother", "buffering", "screen-recording", "coffee", "anime-discussion", "monday", "topless", "pasties", "just-chatting", "moaning", "countdown", "orgasm", "hands-free-cum", "yandere", "all-fours", "malfunctioning-hand", "bed", "high-heels", "boobs", "twerking", "factoid", "cowgirl", "unzip", "belly", "femdom", "gamer-chair", "selfie-camera", "pussy-licking", "pussy-closeup", "fingering", "aftershocks", "dakimakura-discussion", "2000s-music", "ghost-in-the-shell", "lgbtq", "spread-legs", "feet", "footjob", "tit-belt"], + "date": "2020-05-17T00:07:30.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1261810539442946049" +}, { + "tags": null, + "date": "2020-05-20T01:16:56.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1262915175037927425" +}, { + "tags": null, + "date": "2020-07-05T00:02:06.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1279566184690659330" +}, { + "tags": ["just-chatting", "chastity", "recovery", "live-2d", "fetish-research", "house-party", "video-game"], + "date": "2023-01-05T20:58:29.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1611104872484556805" +}, { + "tags": ["singing", "christmas", "karaoke", "just-chatting", "holiday", "lovense-lush", "tomboy", "live2d", "debut", "pegging", "asmr", "strapon-dildo", "erotic-roleplay", "edging"], + "date": "2023-01-13T18:21:13.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1613964398506147853" +}, { + "tags": ["just-chatting", "twintails", "g-string", "swimming-pool", "lovense-lush", "3d", "leap-motion", "orgasm-denial", "moaning", "bad-audio", "toilet", "bidet", "succubus-lingerie", "womb-tattoo", "dressup", "tv-discussion", "states-discussion", "naked", "experimental-lighting", "handjob", "asmr-discussion", "tease"], + "date": "2023-01-20T00:37:06.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1616233319439515648" +}, { + "tags": null, + "date": "2020-07-06T20:03:01.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1280230795085578247" +}, { + "tags": null, + "date": "2020-07-07T20:59:26.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1280607381605449729" +}, { + "tags": null, + "date": "2020-08-22T00:03:48.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1296961229475512323" +}, { + "tags": ["asmr", "3dio", "dominatrix", "submission", "breeding", "ai-dungeon", "tentacles", "anal", "fantasy-writing", "moaning", "cum-ban", "heavy-breathing", "orgasm", "denial", "cum-drunk"], + "date": "2023-01-21T20:36:06.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1616897448890273807" +}, { + "tags": null, + "date": "2020-09-05T23:08:12.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1302383058247614465" +}, { + "tags": null, + "date": "2021-07-04T18:50:39.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1411759377816690696" +}, { + "tags": ["bepis", "pasties", "recovery", "hentai-discussion", "topless", "boobs", "live2d", "handjob", "lovense-lush", "guilty-fap", "train-molestation", "sex-dungeon", "bdsm", "santa", "moaning", "mel-noises", "rickroll", "cucked", "cbat", "punishment2-cum", "orgasm", "milking-device", "cumdrunk", "panting", "heat", "feral", "deep-breathing", "monster-girl", "roleplay", "fucking", "gratitude", "snack", "meltys-quest", "eroge", "fat-bastard", "voiceover", "girl-on-girl", "thirsty", "slut", "good-audio", "love"], + "date": "2023-02-17T02:10:04.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1626403575642353665" +}, { + "tags": ["social-media", "aspirations", "first-time", "lovense-lush", "bad-audio", "ahegao", "3d", "lonely", "friends", "dab", "tag-discussion", "deep-breathing", "jerk-off-gesture", "gratitude", "cat-girls", "plexstorm", "hentai-game", "cute-pussy", "origin", "moaning", "twerking", "dancing", "shaking", "strip-tease", "anime-discussion", "video-game-discussion", "ass", "pasties", "high-heels", "harness", "thong", "leggings", "technical-difficulties", "panic-attack", "meditation", "just-chatting", "masturbation", "nipples"], + "date": "2020-02-07T23:21:48.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1225922638687752192" +}, { + "tags": null, + "date": "2020-10-16T23:32:50.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1317247159478136832" +}, { + "tags": ["bad-audio", "new-outfit", "anniversary", "bunny-outfit", "3d", "sexmachine", "raul", "recovery", "bunny-suit", "sit-on-face", "naked", "jacket", "mel-noises", "rickroll", "frickenator", "standing-cum", "legwarmers", "bunny-ears", "ass", "leg-shaking", "glitch", "doggy-style", "breeding", "dirty-talk", "cum-drunk", "trouble-walking", "slut", "food", "masturbation", "dick-sucking", "t1m", "lovense-lush", "pool", "flooring"], + "date": "2023-02-12T23:07:09.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1624907994671489026" +}, { + "tags": ["mel-noises", "crying", "good-audio", "live2d", "moaning", "dildo", "masturbation", "glass-dildo", "teaser", "sex-stories", "horny", "script-writing", "joi", "topless", "pasties", "naked", "asmr", "just-chatting", "self-care", "deep-breathing", "guided-meditation", "tantric-sex", "slut", "orgasm", "hentai-game", "caring"], + "date": "2023-02-09T00:25:47.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1623478232036122624" +}, { + "tags": ["just-chatting", "community", "anxiety", "dork", "live2d", "nut-between-worlds", "pro-social-behavior", "topless", "pasties", "harness", "boobs", "moaning", "blowjob"], + "date": "2023-02-13T03:07:00.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1624907994671489026" +}, { + "tags": null, + "date": "2020-03-01T01:14:02.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1233923415964340225" +}, { + "tags": null, + "date": "2020-03-28T01:42:21.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1243715015829585925" +}, { + "tags": null, + "date": "2020-04-01T00:13:26.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1245142190952796167" +}, { + "tags": null, + "date": "2020-05-02T22:01:33.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1256705414663725058" +}, { + "tags": null, + "date": "2020-06-10T18:58:26.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1270792457941368832" +}, { + "tags": null, + "date": "2020-06-28T00:10:07.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1277031489205678083" +}, { + "tags": null, + "date": "2020-02-25T21:31:41.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1232417908678168576" +}, { + "tags": null, + "date": "2020-03-10T22:00:51.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1237498680690855939" +}, { + "tags": null, + "date": "2020-03-20T15:41:36.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1241027117284044801" +}, { + "tags": null, + "date": "2020-08-28T23:03:16.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1299482712869306368" +}, { + "tags": null, + "date": "2020-09-24T23:25:58.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1309272896456527872" +}, { + "tags": null, + "date": "2020-10-13T23:03:14.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1316152545732157448" +}, { + "tags": null, + "date": "2020-10-23T18:55:31.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1319714083462107139" +}, { + "tags": ["just-chatting", "lovense-lush", "mel-noises", "pro-social-behavior", "topless", "g-string", "thong", "high-heels", "selfie-camera", "ass", "porn-discussion", "panties", "no-nut-november", "moaning", "feet", "feet-licking", "vtuber-discussion", "gamer-chair", "naked", "stretching", "flexibility", "orgasm", "masturbation"], + "date": "2020-11-09T00:55:59.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1325603004695842816" +}, { + "tags": null, + "date": "2020-12-14T21:46:27.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1338601272354758656" +}, { + "tags": null, + "date": "2020-12-24T23:02:13.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1342244216731275265" +}, { + "tags": null, + "date": "2020-08-11T23:01:22.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1293321639728492544" +}, { + "tags": null, + "date": "2020-10-11T22:05:30.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1315413239556190211" +}, { + "tags": null, + "date": "2021-03-03T23:03:48.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1367249376675041285" +}, { + "tags": null, + "date": "2021-04-18T00:30:03.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1383578535340634120" +}, { + "tags": null, + "date": "2021-04-23T23:01:52.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1385730672661303296" +}, { + "tags": null, + "date": "2021-06-06T20:55:39.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1401643974922838019" +}, { + "tags": null, + "date": "2021-06-15T22:57:44.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1404936189946236930" +}, { + "tags": null, + "date": "2021-07-27T23:04:15.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1420158120370872320" +}, { + "tags": null, + "date": "2021-08-30T23:08:30.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1432480377302552587" +}, { + "tags": null, + "date": "2021-09-03T21:59:09.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1433912474542264321" +}, { + "tags": null, + "date": "2021-02-05T22:57:21.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1357825669024120841" +}, { + "tags": null, + "date": "2021-10-04T22:20:50.000Z", + "announceUrl": "http://twitter.com/ProjektMelody/status/1445151953764458501" +}, { + "tags": null, + "date": "2021-11-16T23:07:06.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1460746277717782528" +}, { + "tags": null, + "date": "2021-12-22T23:58:43.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1473805228537438209" +}, { + "tags": null, + "date": "2022-01-15T20:53:35.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1482455948459450369" +}, { + "tags": null, + "date": "2022-01-28T22:13:21.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1487187063048921093" +}, { + "tags": null, + "date": "2022-02-13T21:17:23.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1492971187047374849" +}, { + "tags": null, + "date": "2022-04-13T20:10:50.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1514335322527649798" +}, { + "tags": null, + "date": "2022-05-14T23:02:18.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1525612497935507457" +}, { + "tags": null, + "date": "2022-05-26T18:31:28.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1529892992818876417" +}, { + "tags": null, + "date": "2022-08-01T19:28:31.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1554187338804338696" +}, { + "tags": null, + "date": "2022-09-02T20:06:16.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1565793017960828931" +}, { + "tags": null, + "date": "2022-09-23T23:38:18.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1573456755236954114" +}, { + "tags": ["memes", "anime", "bdsm", "orgasm", "moaning", "laughing", "cum-drunk", "lovense-lush"], + "date": "2022-10-27T23:20:05.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1585773359379677184" +}, { + "tags": ["lovense-hyphy", "lovense-lush", "whispering", "asmr", "sportscar", "pee", "sick", "coughing", "shaking", "cum", "orgasm", "brainfrog", "oral", "sucking", "sex-toy", "cum-drunk", "moaning", "twitching", "thrusting", "grinding", "objectification", "flooring", "keyboard-clicking", "mocap-failure", "muted", "charades", "posing", "riding", "dry-humping", "ass", "high-heels", "legs", "hip-sway", "plug-suit", "chest-harness", "naked", "womb-tattoo", "sexy-dance", "slav-squat", "tail", "pole-dance", "clipping", "licking", "glowing", "dab"], + "date": "2022-11-12T00:13:40.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1591222662487236609" +}, { + "tags": ["eroge", "video-game", "orc", "moaning", "cyber", "cum-drunk", "orgasm", "towel", "petting", "massage", "accupressure", "lingerie", "ass", "thong", "pasties", "adhesive-bandage", "monster-girls", "texting", "lovense-lush", "lovense-hyphy", "broken-toy", "tail", "live2d", "food-porn", "self-care", "aromatherapy", "monster-dick", "hot-sluts", "monster", "fantasy", "cock-sleeve", "feet", "footjob", "fingering", "elf", "cum-whore", "panties", "cum"], + "date": "2022-11-19T22:35:57.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1594097172689166337" +}, { + "tags": ["ass", "tail", "lovense-lush", "conversation", "grinding", "flooring", "going-away", "humping", "laughing", "eating", "harness", "boobs", "toes", "cum-drunk", "aftershocks", "dancing", "pole-dancing", "moaning", "feet", "stepping", "cock-abuse", "extreme-closeup", "butthole", "panties", "shaking", "pussy-licking", "singing", "hentai", "carnival", "fingering", "wedgie", "fetishwear", "watch-along", "pasties", "creampie", "rude-sex", "cumming", "g-string", "idol", "voyeurism", "condom", "gay", "spitting", "pervert", "edging", "mutual-masturbation", "sniffing"], + "date": "2022-11-22T23:04:43.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1595191577068044289" +}, { + "tags": ["hentai", "eroge", "dohna-dohna", "orgasm-denial", "chastity", "video-game", "live2d", "ovulation", "feet", "horny", "dream", "vampire", "just-chatting", "numi", "game-of-thrones", "recovery", "bad-audio", "yandere", "storytime", "spontaneous-song", "crushes"], + "date": "2023-01-01T22:03:49.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1609671764514164737" +}, { + "tags": null, + "date": "2021-09-07T22:01:24.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1435362590893527040" +}, { + "tags": null, + "date": "2021-09-16T20:00:39.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1438593697445302274" +}, { + "tags": null, + "date": "2022-02-07T23:27:14.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1490829536316985348" +}, { + "tags": ["lawnmower", "motorbunny", "bad-audio", "3d", "lovense-lush", "pussy-to-mouth", "screaming", "swimming-pool", "moaning", "deep-breathing", "cock-riding", "mel-noises", "mind-break", "technical-difficulties", "yelling", "echo", "reverse-cowgirl", "muffled-screams", "orgasm", "no-face-tracking", "swearing", "chaturbate-compliance", "spit", "messy", "cum-drinking", "clit-milking-device", "multiple-orgasms", "4-cum", "squirting", "loud-orgasm", "cum-chalice", "schlorp", "sksksksk"], + "date": "2023-01-25T00:19:39.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1618040869587947521" +}, { + "tags": ["lovense-lush", "lovense-hush", "tail", "butt-plug", "double-penetration", "moaning", "blushing", "manual-masturbation", "embarrasment", "orgasm", "just-chatting", "shorts", "stripping", "jiggle-physics", "3d", "boobs", "pasties", "dancing", "swearing", "vtuber-discussion", "t-pose", "ass", "anime-discussion", "selfie-camera", "edging", "jojo-posing", "anal-fingering", "fingering"], + "date": "2020-11-18T01:59:20.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1328880436551049217" +}, { + "tags": null, + "date": "2021-01-03T23:01:53.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1345868009483735042" +}, { + "tags": null, + "date": "2021-01-10T19:57:37.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1348358353676873729" +}, { + "tags": null, + "date": "2021-02-07T23:04:03.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1358552132295798787" +}, { + "tags": ["boobs", "porn-game", "video-game", "bondage", "mobile-game", "android", "moaning", "bikini", "blowjob", "doggy-style", "3d", "sponsored"], + "date": "2022-11-18T01:03:45.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1593409592477638656" +}, { + "tags": ["lovense-lush", "spanking", "ass", "stripping", "bottom-bitch", "permission", "cum-slut", "blowjob", "grinding", "yoga", "cyber", "kegel", "conversation", "bend-over", "squirming", "horny", "dildo-choking", "deep-throating", "finger-stimulation", "begging", "hummer", "orgasm", "cum-drunk", "laughing", "gratitude", "teasing", "crying", "bullying", "moaning", "feet-licking", "womb-tattoo", "harness", "boobs", "vagina", "drugs", "interpretive-dance", "sexy-dancing", "pole-dancing", "flooring", "jig", "silly-dancing", "tail"], + "date": "2022-11-22T01:15:55.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1594862203546501121" +}, { + "tags": ["love-drunk", "noises", "x-mas", "technical-difficulties", "horny", "chat-analytics", "chat-roast", "lovense-lush", "moaning", "self-deprecation-humor", "edging", "just-chatting", "orgasm", "2-cum", "cum-drunk", "multiple-orgasms", "hands-free-cum", "handcuffs", "butt-plug", "anal", "chastity-device", "bdsm", "ball-gag", "collar", "heavy-breathing", "big-o", "snacking", "fat-bastard", "airplane", "mile-high-club", "dirty-talk", "video-game", "malady", "hime-hajime", "big-bang-studios", "a-nut-between-worlds"], + "date": "2022-12-17T22:14:20.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1604238592389238784" +}, { + "tags": ["video-game", "recovery", "bad-audio", "technical-difficulties", "just-chatting", "purino-party", "zooted", "medication", null], + "date": "2023-01-05T02:07:18.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1610820355895037955" +}, { + "tags": ["cum-ban", "soaking", "semantics", "lovense-lush", "dildo", "pussy-noises", "relaxation", "meditation", "games", "meltys-quest", "wet", "asmr", "messy", "moaning", "horny", "blowjob", "doctor-defiance", "cum-brain", "mind-break", "giggles", "mel-noises", "hentai-game", "prank", "ear-rape", "live2d", "voice-acting", "goblin-sex", "drake__selfsuck"], + "date": "2023-01-09T23:07:18.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1612586844298018817" +}, { + "tags": ["bondage", "digital-art", "lovense-lush", "mel-noises", "moaning", "hucow", "pussy-milking", "browser-wars", "edging", "daddy-play", "orgasm", "feet", "live2d", "rapping", "singing", "vibrator", "good-audio", "vibrator-asmr", "spreader-bars", "precum", "puddle", "bdsm", "restraint", "slut", "cum-drunk", "loud-orgasm", "multiple-orgasms", "screaming", "begging", "cum-inside", "mind-break", "aftershocks", "labia-spreader"], + "date": "2023-01-29T22:06:21.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1619819262125944832" +}, { + "tags": ["pasties", "topless", "live2d", "lovense-lush", "lovense-osci", "orgasm", "cum-drunk", "mind-break", "moaning", "mel-noises", "multiple-orgasms", "hentai-watch-along", "dirty-talk", "blowjob", "horny", "tentacles", "double-penetration", "fantasy"], + "date": "2023-02-01T01:38:32.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1620597436988411907" +}, { + "tags": ["cooking", "onigiri", "seiso", "irl", "bad-audio", "rickroll", "dirty-slut", "crepe", "jokes", "just-chatting", "sex-toy-discussion", "booli", "femdom", "topless", "apron", "boobs", "nipples", "bacon", "innuendo", "dirty-talk", "parenting", "stories"], + "date": "2023-02-24T00:17:38.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1628911998787362817" +}, { + "tags": ["3d", "lovense-lush", "lovense-sex-machine", "pool", "dancing", "sway", "ass", "shorts", "leggings", "middrift", "mel-noises", "pig-latin", "moaning", "topless", "bra", "bottomless", "good-audio", "kneeling", "legs-spread", "t1m", "pussy", "grinding", "shaking", "missionary-style", "first-time", "lovense-hyphy", "cum", "orgasm", "multiple-orgasms", "begging", "prone-bone", "daddy", "dirty-talk", "shibari", "closeup", "pov", "6-cums"], + "date": "2023-03-04T02:28:14.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1631843966843863040" +}, { + "tags": ["anime-girl", "horny", "baka", "lovense-lush", "boobs", "harness", "pasties", "cum", "orgasm", "edging", "gunrun", "hormones", "subathon", "fantasy", "sake", "ntrpg", "game-night", "toilet", "ntr", "elderly", "fatherboard", "sneeze", "dream-daddy", "futa-fix-dick-dine-and-dash", "futanari", "asmr", "lovense-hush", "anal", "begging"], + "date": "2023-03-03T02:04:05.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1631475499753979908" +}, { + "tags": ["lovense-partnership", "big-bang-a-nut-between-worlds", "selfcest", "cuck", "video-game", "sponsored-stream", "finger-blasting", "blowjob", "handjob"], + "date": "2023-03-24T21:13:29.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1639374902246404102" +}, { + "tags": ["bikini", "womb-tattoo", "pool", "lovense-lush", "japan", "adhd", "edging", "permission", "cum-slut", "mel-noises", "moaning", "begging", "yelling", "mindbreak", "cum-drunk", "dirty-talk", "bbc", "dildo", "death", "lovense-sex-machine", "bakery", "orgasms", "multiple-orgasms", "ejaculate", "blowjob", "just-chatting", "choking", "joi", "slave", "master", "deepthroat", "sloppy", "3d", "goblin", "anal-breeding", "rapping", "dj", "dancing", "hime-hajime", "bad-audio", "voice-acting", "alto"], + "date": "2023-03-23T21:13:04.000Z", + "announceUrl": "https://twitter.com/ProjektMelody/status/1639012409175072769" +}] \ No newline at end of file diff --git a/packages/strapi/favicon.png b/packages/strapi/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..df668a881b58175effdf6e6d667cd0a29ce98075 GIT binary patch literal 497 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!Vgr0aT!FNQ%YPz~r|W+YAaeN! zM4m4Hfm|S%I{E+RP5<}p{hyZnKR5G#ea-*Q_WyV8{15c~egC1S$L^sn@(SnS%da63LSXaQ`%;7gOVpveiyr7nQL9IZ= z-A`pVKgIF>S>*T2kpBVWANdax|AfbLJG@L@l;9x3(=mx{2S>{97;cxH>_3zyyzXAU zjjdxQ^WxPEN$-xlJne8sF0xqg#Mjh={AZcjd`yxKaLoVx;=zr@liB5BU%YW{PhSw- uckQM0zm%HSf9!WG&HHyg{qyDQlnaA`#FRum$ErX}4pUXO@geCwXjqE-E literal 0 HcmV?d00001 diff --git a/packages/strapi/misc/2023-05-26-export-og-tags.js b/packages/strapi/misc/2023-05-26-export-og-tags.js new file mode 100644 index 0000000..522e3e8 --- /dev/null +++ b/packages/strapi/misc/2023-05-26-export-og-tags.js @@ -0,0 +1,202 @@ +require('dotenv').config() + +const { Client } = require('pg') +const fetch = require('node-fetch') +const _ = require('lodash'); + + + +// module.exports = { +// async up(knex) { + +// // Get all VODs from the database +// const vods = await knex.select('*').from('vods'); + +// // sanity check every B2 URL +// for (const vod of vods) { +// await checkUrl(vod.video_src) +// } + +// console.log(`there are ${problemUrls.length} the problem urls`) +// console.log(problemUrls) + +// process.exit(5923423) +// }, +// }; + +// const slugify = require('slugify') + + +// function slugifyString (str) { +// return slugify(str, { +// replacement: '-', // replace spaces with replacement character, defaults to `-` +// remove: undefined, // remove characters that match regex, defaults to `undefined` +// lower: true, // convert to lower case, defaults to `false` +// strict: true, // strip special characters except replacement, defaults to `false` +// locale: 'en', // language code of the locale to use +// trim: true // trim leading and trailing replacement chars, defaults to `true` +// }) +// } + + +async function associateTagWithVodsInStrapi (tagId, vodsIds) { + const res = await fetch(`${process.env.STRAPI_URL}/api/tags/${tagId}`, { + method: 'PUT', + headers: { + 'authorization': `Bearer ${process.env.STRAPI_API_KEY}` + }, + data: { + vods: [vodsIds] + } + }) + const json = await res.json() + + + if (!res.ok) throw new Error(JSON.stringify(json)) +} + + + +async function associateVodWithTagsInStrapi (vodId, tagsIds) { + const res = await fetch(`${process.env.STRAPI_URL}/api/vods/${vodId}?populate=*`, { + method: 'PUT', + headers: { + 'authorization': `Bearer ${process.env.STRAPI_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + data: { + tags: tagsIds + } + }) + }) + const json = await res.json() + + + if (!res.ok) throw new Error(JSON.stringify(json)) +} + +async function getStrapiVodByAnnounceUrl (announceUrl) { + const res = await fetch(`${process.env.STRAPI_URL}/api/vods?filters[announceUrl][$eqi]=${announceUrl}`, { + method: 'GET', + headers: { + 'authorization': `Bearer ${process.env.STRAPI_API_KEY}` + } + }) + const json = await res.json() + return json.data[0] +} + + +// async function getStrapiVodByDate (date) { + +// // const r = await fetch(`${process.env.STRAPI_URL}/api/vods/200`, { +// // method: 'GET', +// // headers: { +// // 'authorization': `Bearer ${process.env.STRAPI_API_KEY}` +// // } +// // }) +// // const j = await r.json() + +// const res = await fetch(`${process.env.STRAPI_URL}/api/vods?filters[date][$eqi]=${date}`, { +// method: 'GET', +// headers: { +// 'authorization': `Bearer ${process.env.STRAPI_API_KEY}` +// } +// }) +// const json = await res.json() +// process.exit(3) +// if (!res.ok) throw new Error(JSON.stringify(json)); +// return json.id +// } + + +async function getStrapiTagByName (tag) { + const res = await fetch(`${process.env.STRAPI_URL}/api/tags?filters[name][$eqi]=${tag}`, { + method: 'GET', + headers: { + 'authorization': `Bearer ${process.env.STRAPI_API_KEY}` + } + }) + const json = await res.json() + return json.data[0] +} + +async function main () { + const client = new Client() + await client.connect() + + + // get list of vods from our source db + const vodsResponse = await client.query('SELECT tags, date, "announceUrl" FROM vod') + + console.log(JSON.stringify(vodsResponse.rows)) + process.exit(5) + + for (const vod of vodsResponse.rows) { + + + + // get matching vod in strapi + const strapiVod = await getStrapiVodByAnnounceUrl(vod.announceUrl) + + if (strapiVod) { + // we've got a matching vod + + if (vod.tags) { + console.log(`source vod has tags: ${vod.tags}`) + + let strapiTagsIds = [] + + // for each tag, get the matching strapi tag ID + for (const tag of vod.tags) { + // lookup the strapi tag id + const strapiTag = await getStrapiTagByName(tag) + strapiTagsIds.push(strapiTag.id) + } + + console.log(`we are adding the following strapiTagsIds to vod ID ${strapiVod.id}: ${strapiTagsIds}`) + + // create relations between matching vod and the tags + await associateVodWithTagsInStrapi(strapiVod.id, strapiTagsIds) + + } + } + } + + // const groupedCollection = _.groupBy(related.rows, 'vod_id'); + // for (const vodId in groupedCollection) { + // const tagsIds = groupedCollection[vodId].map((t)=>t.tag_id) + // const tagsIdsAltered = tagsIds.map((t)=>(t === 114) ? 520 : t) + // await associateVodWithTagsInStrapi(vodId, tagsIdsAltered) + // } + + // const res = await client.query('SELECT id, name FROM tags') + // for (const tag of res.rows) { + // // for (const link of related.rows) { + // // await new Promise((resolve) => setTimeout(resolve, 1000)) + // // await associateTagWithVodsInStrapi(link.tag_id, link.vod_id) + // // } + // } + + await client.end() +} + + + + + +// const res = await client.query('SELECT * FROM public.tags_vod_links') + + +main() +// restore tags from db backup +// make associations between vods <--> tags in strapi + + +// we iterate through the local, non-strapi backup db first. +// get list of all tags +// for each tag +// * get list of related vods +// * create relation in Strapi. + diff --git a/packages/strapi/misc/generateCuid.js b/packages/strapi/misc/generateCuid.js new file mode 100644 index 0000000..c049924 --- /dev/null +++ b/packages/strapi/misc/generateCuid.js @@ -0,0 +1,7 @@ +const { init } = require('@paralleldrive/cuid2'); + +module.exports = function() { + const length = 10; + const genCuid = init({ length }); + return genCuid(); +} \ No newline at end of file diff --git a/packages/strapi/package.json b/packages/strapi/package.json new file mode 100644 index 0000000..764c23f --- /dev/null +++ b/packages/strapi/package.json @@ -0,0 +1,90 @@ +{ + "name": "futureporn-strapi", + "private": true, + "version": "0.1.0", + "description": "A Strapi application", + "scripts": { + "dev:c": "concurrently \"npm:tunnel\" \"npm:dev:strapi\"", + "tunnel": "ngrok start futureporn-strapi", + "chisel": "bash ./chisel.sh", + "db": "bash ./database/devDb.sh", + "start": "strapi start", + "build": "strapi build", + "develop": "NODE_ENV=development strapi develop", + "strapi": "strapi", + "preinstall": "npx only-allow yarn" + }, + "dependencies": { + "@11ty/eleventy-fetch": "^4.0.0", + "@aws-sdk/client-s3": "^3.485.0", + "@esm2cjs/execa": "6.1.1-cjs.1", + "@mux/mux-node": "^7.3.3", + "@paralleldrive/cuid2": "^2.2.2", + "@radix-ui/react-use-callback-ref": "^1.0.1", + "@strapi/plugin-i18n": "4.17.0", + "@strapi/plugin-users-permissions": "4.17.0", + "@strapi/provider-email-sendgrid": "4.17.0", + "@strapi/provider-upload-cloudinary": "4.17.0", + "@strapi/strapi": "4.17.0", + "@strapi/utils": "4.17.0", + "@testing-library/dom": "8.19.0", + "@testing-library/react": "12.1.4", + "@testing-library/react-hooks": "8.0.1", + "@testing-library/user-event": "14.4.3", + "aws-sdk": "^2.1539.0", + "bcryptjs": "2.4.3", + "better-sqlite3": "8.0.1", + "canvas": "^2.11.2", + "codemirror": "^6.0.1", + "css-loader": "^6.8.1", + "cuid": "^3.0.0", + "date-fns": "^3.1.0", + "formik": "2.2.9", + "fuzzy-search": "^3.2.1", + "grant-koa": "5.4.8", + "history": "^4.10.1", + "immer": "9.0.19", + "jsonwebtoken": "9.0.0", + "jwk-to-pem": "2.0.5", + "koa": "^2.15.0", + "koa2-ratelimit": "^1.1.3", + "lodash": "4.17.21", + "match-sorter": "^4.2.1", + "msw": "1.0.1", + "node-abort-controller": "^3.1.1", + "object-assign": "^4.1.1", + "pg": "^8.11.3", + "prop-types": "^15.8.1", + "purest": "4.0.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-intl": "6.3.2", + "react-query": "3.24.3", + "react-redux": "8.0.5", + "react-router-dom": "5.3.4", + "react-test-renderer": "^17.0.2", + "semver": "^7.5.4", + "sharp": "^0.32.6", + "strapi-plugin-fuzzy-search": "^2.2.0", + "styled-components": "5.3.3", + "typescript": "^5.3.3", + "url-join": "4.0.1", + "yallist": "^4.0.0", + "yup": "^0.32.11" + }, + "devDependencies": { + "concurrently": "^8.2.2" + }, + "author": { + "name": "CJ_Clippy" + }, + "strapi": { + "uuid": false + }, + "engines": { + "node": ">=14.19.1 <=18.x.x", + "npm": ">=6.0.0" + }, + "license": "MIT", + "packageManager": "yarn@1.22.19" +} diff --git a/packages/strapi/public/robots.txt b/packages/strapi/public/robots.txt new file mode 100644 index 0000000..ff5d316 --- /dev/null +++ b/packages/strapi/public/robots.txt @@ -0,0 +1,3 @@ +# To prevent search engines from seeing the site altogether, uncomment the next two lines: +# User-Agent: * +# Disallow: / diff --git a/packages/strapi/public/uploads/.gitkeep b/packages/strapi/public/uploads/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/strapi/src/admin/app.example.js b/packages/strapi/src/admin/app.example.js new file mode 100644 index 0000000..45cad61 --- /dev/null +++ b/packages/strapi/src/admin/app.example.js @@ -0,0 +1,39 @@ +const config = { + locales: [ + // 'ar', + // 'fr', + // 'cs', + // 'de', + // 'dk', + // 'es', + // 'he', + // 'id', + // 'it', + // 'ja', + // 'ko', + // 'ms', + // 'nl', + // 'no', + // 'pl', + // 'pt-BR', + // 'pt', + // 'ru', + // 'sk', + // 'sv', + // 'th', + // 'tr', + // 'uk', + // 'vi', + // 'zh-Hans', + // 'zh', + ], +}; + +const bootstrap = (app) => { + console.log(app); +}; + +export default { + config, + bootstrap, +}; diff --git a/packages/strapi/src/admin/webpack.config.example.js b/packages/strapi/src/admin/webpack.config.example.js new file mode 100644 index 0000000..1ca45c2 --- /dev/null +++ b/packages/strapi/src/admin/webpack.config.example.js @@ -0,0 +1,9 @@ +'use strict'; + +/* eslint-disable no-unused-vars */ +module.exports = (config, webpack) => { + // Note: we provide webpack above so you should not `require` it + // Perform customizations to webpack config + // Important: return the modified config + return config; +}; diff --git a/packages/strapi/src/api/.gitkeep b/packages/strapi/src/api/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/strapi/src/api/b2-file/content-types/b2-file/schema.json b/packages/strapi/src/api/b2-file/content-types/b2-file/schema.json new file mode 100644 index 0000000..6992f65 --- /dev/null +++ b/packages/strapi/src/api/b2-file/content-types/b2-file/schema.json @@ -0,0 +1,36 @@ +{ + "kind": "collectionType", + "collectionName": "b2_files", + "info": { + "singularName": "b2-file", + "pluralName": "b2-files", + "displayName": "B2 File", + "description": "" + }, + "options": { + "draftAndPublish": false + }, + "pluginOptions": {}, + "attributes": { + "url": { + "type": "string", + "required": true, + "unique": true + }, + "key": { + "type": "string", + "unique": true, + "required": true + }, + "uploadId": { + "type": "string", + "unique": true, + "required": true + }, + "cdnUrl": { + "type": "string", + "unique": true, + "required": true + } + } +} diff --git a/packages/strapi/src/api/b2-file/controllers/b2-file.js b/packages/strapi/src/api/b2-file/controllers/b2-file.js new file mode 100644 index 0000000..65f936a --- /dev/null +++ b/packages/strapi/src/api/b2-file/controllers/b2-file.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * b2-file controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::b2-file.b2-file'); diff --git a/packages/strapi/src/api/b2-file/routes/b2-file.js b/packages/strapi/src/api/b2-file/routes/b2-file.js new file mode 100644 index 0000000..a74a8d9 --- /dev/null +++ b/packages/strapi/src/api/b2-file/routes/b2-file.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * b2-file router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +module.exports = createCoreRouter('api::b2-file.b2-file'); diff --git a/packages/strapi/src/api/b2-file/services/b2-file.js b/packages/strapi/src/api/b2-file/services/b2-file.js new file mode 100644 index 0000000..03e2bdf --- /dev/null +++ b/packages/strapi/src/api/b2-file/services/b2-file.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * b2-file service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::b2-file.b2-file'); diff --git a/packages/strapi/src/api/contributor/content-types/contributor/schema.json b/packages/strapi/src/api/contributor/content-types/contributor/schema.json new file mode 100644 index 0000000..3cc046c --- /dev/null +++ b/packages/strapi/src/api/contributor/content-types/contributor/schema.json @@ -0,0 +1,30 @@ +{ + "kind": "collectionType", + "collectionName": "contributors", + "info": { + "singularName": "contributor", + "pluralName": "contributors", + "displayName": "Contributor", + "description": "" + }, + "options": { + "draftAndPublish": false + }, + "pluginOptions": {}, + "attributes": { + "name": { + "type": "string", + "required": true + }, + "url": { + "type": "string" + }, + "isFinancialDonor": { + "type": "boolean", + "default": false + }, + "isVodProvider": { + "type": "boolean" + } + } +} diff --git a/packages/strapi/src/api/contributor/controllers/contributor.js b/packages/strapi/src/api/contributor/controllers/contributor.js new file mode 100644 index 0000000..22b9cf6 --- /dev/null +++ b/packages/strapi/src/api/contributor/controllers/contributor.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * contributor controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::contributor.contributor'); diff --git a/packages/strapi/src/api/contributor/routes/contributor.js b/packages/strapi/src/api/contributor/routes/contributor.js new file mode 100644 index 0000000..cf61a59 --- /dev/null +++ b/packages/strapi/src/api/contributor/routes/contributor.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * contributor router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +module.exports = createCoreRouter('api::contributor.contributor'); diff --git a/packages/strapi/src/api/contributor/services/contributor.js b/packages/strapi/src/api/contributor/services/contributor.js new file mode 100644 index 0000000..ca75442 --- /dev/null +++ b/packages/strapi/src/api/contributor/services/contributor.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * contributor service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::contributor.contributor'); diff --git a/packages/strapi/src/api/goal/content-types/goal/schema.json b/packages/strapi/src/api/goal/content-types/goal/schema.json new file mode 100644 index 0000000..0580c6f --- /dev/null +++ b/packages/strapi/src/api/goal/content-types/goal/schema.json @@ -0,0 +1,30 @@ +{ + "kind": "collectionType", + "collectionName": "goals", + "info": { + "singularName": "goal", + "pluralName": "goals", + "displayName": "Goal", + "description": "" + }, + "options": { + "draftAndPublish": false + }, + "pluginOptions": {}, + "attributes": { + "amountCents": { + "type": "string", + "required": true, + "unique": true + }, + "description": { + "type": "string", + "required": true, + "unique": true + }, + "name": { + "type": "string", + "unique": true + } + } +} diff --git a/packages/strapi/src/api/goal/controllers/goal.js b/packages/strapi/src/api/goal/controllers/goal.js new file mode 100644 index 0000000..2b8e3a3 --- /dev/null +++ b/packages/strapi/src/api/goal/controllers/goal.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * goal controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::goal.goal'); diff --git a/packages/strapi/src/api/goal/routes/goal.js b/packages/strapi/src/api/goal/routes/goal.js new file mode 100644 index 0000000..e836728 --- /dev/null +++ b/packages/strapi/src/api/goal/routes/goal.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * goal router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +module.exports = createCoreRouter('api::goal.goal'); diff --git a/packages/strapi/src/api/goal/services/goal.js b/packages/strapi/src/api/goal/services/goal.js new file mode 100644 index 0000000..6c047ac --- /dev/null +++ b/packages/strapi/src/api/goal/services/goal.js @@ -0,0 +1,11 @@ +'use strict'; + +/** + * goal service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::goal.goal'); + + diff --git a/packages/strapi/src/api/gogs/content-types/gogs/schema.json b/packages/strapi/src/api/gogs/content-types/gogs/schema.json new file mode 100644 index 0000000..d933d5d --- /dev/null +++ b/packages/strapi/src/api/gogs/content-types/gogs/schema.json @@ -0,0 +1,24 @@ +{ + "kind": "singleType", + "collectionName": "gogss", + "info": { + "singularName": "gogs", + "pluralName": "gogss", + "displayName": "Gogs" + }, + "options": { + "draftAndPublish": false + }, + "pluginOptions": {}, + "attributes": { + "apiKey": { + "type": "string", + "required": true + }, + "url": { + "type": "string", + "required": true, + "default": "https://git.futureporn.net" + } + } +} diff --git a/packages/strapi/src/api/gogs/controllers/gogs.js b/packages/strapi/src/api/gogs/controllers/gogs.js new file mode 100644 index 0000000..5d83874 --- /dev/null +++ b/packages/strapi/src/api/gogs/controllers/gogs.js @@ -0,0 +1,29 @@ +'use strict'; + +/** + * gogs controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::gogs.gogs', ({ strapi }) => ({ + issues: async (ctx) => { + try { + // Fetch the 'gogs' single type from Strapi + const gogsConfig = await strapi.query('api::gogs.gogs').findOne(); + + if (!gogsConfig) { + return ctx.badRequest('Gogs configuration not found'); + } + + const { url, apiKey } = gogsConfig; + const openIssues = await strapi.service('api::gogs.gogs').fetchAllPagesFromGogsAPI(`${url}/api/v1/repos/futureporn/pm/issues?state=open`, apiKey) + const closedIssues = await strapi.service('api::gogs.gogs').fetchAllPagesFromGogsAPI(`${url}/api/v1/repos/futureporn/pm/issues?state=closed`, apiKey) + + return { openIssues, closedIssues } + } catch (error) { + console.error('Error fetching Gogs issues:', error); + return ctx.badRequest('Failed to fetch issues from Gogs'); + } + } +})); \ No newline at end of file diff --git a/packages/strapi/src/api/gogs/routes/gogs.js b/packages/strapi/src/api/gogs/routes/gogs.js new file mode 100644 index 0000000..50c6544 --- /dev/null +++ b/packages/strapi/src/api/gogs/routes/gogs.js @@ -0,0 +1,34 @@ +'use strict'; + +/** + * gogs router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +const defaultRouter = createCoreRouter('api::gogs.gogs'); + +// greets https://forum.strapi.io/t/how-to-add-custom-routes-to-core-routes-in-strapi-4/14070/7 +const customRouter = (innerRouter, extraRoutes = []) => { + let routes; + return { + get prefix() { + return innerRouter.prefix; + }, + get routes() { + if (!routes) routes = extraRoutes.concat(innerRouter.routes) + return routes; + }, + }; +}; + +const myExtraRoutes = [ + { + method: "GET", + path: "/gogs/issues", + handler: "api::gogs.gogs.issues" + } +]; + +module.exports = customRouter(defaultRouter, myExtraRoutes); + diff --git a/packages/strapi/src/api/gogs/services/gogs.js b/packages/strapi/src/api/gogs/services/gogs.js new file mode 100644 index 0000000..13afb2f --- /dev/null +++ b/packages/strapi/src/api/gogs/services/gogs.js @@ -0,0 +1,42 @@ +'use strict'; + +/** + * gogs service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + + + +module.exports = createCoreService('api::gogs.gogs', ({ strapi }) => ({ + + async fetchAllPagesFromGogsAPI(url, apiKey) { + + // Fetch the first page + const response = await fetch(url, { + headers: { + 'Authorization': `token ${apiKey}` + } + }); + + if (!response.ok) { + throw new Error(`Request failed with status: ${response.status}`); + } + + const data = await response.json(); + + // Check if there are more pages available + if (response.headers.has('link')) { + const linkHeader = response.headers.get('link'); + const nextPageMatch = /<([^>]+)>;\s*rel="next"/.exec(linkHeader); + + if (nextPageMatch) { + const nextPageUrl = nextPageMatch[1]; + const nextPageData = await this.fetchAllPagesFromGogsAPI(nextPageUrl, apiKey); + return [...data, ...nextPageData]; + } + } + + return data; + } +})) \ No newline at end of file diff --git a/packages/strapi/src/api/issue/content-types/issue/schema.json b/packages/strapi/src/api/issue/content-types/issue/schema.json new file mode 100644 index 0000000..b8c6768 --- /dev/null +++ b/packages/strapi/src/api/issue/content-types/issue/schema.json @@ -0,0 +1,36 @@ +{ + "kind": "collectionType", + "collectionName": "issues", + "info": { + "singularName": "issue", + "pluralName": "issues", + "displayName": "issue", + "description": "" + }, + "options": { + "draftAndPublish": false + }, + "pluginOptions": {}, + "attributes": { + "url": { + "type": "string", + "required": true + }, + "sla": { + "type": "enumeration", + "enum": [ + "public", + "patron", + "authenticated" + ], + "default": "public", + "required": true + }, + "type": { + "type": "enumeration", + "enum": [ + "stall" + ] + } + } +} diff --git a/packages/strapi/src/api/issue/controllers/issue.js b/packages/strapi/src/api/issue/controllers/issue.js new file mode 100644 index 0000000..a9c83e3 --- /dev/null +++ b/packages/strapi/src/api/issue/controllers/issue.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * issue controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::issue.issue'); diff --git a/packages/strapi/src/api/issue/routes/issue.js b/packages/strapi/src/api/issue/routes/issue.js new file mode 100644 index 0000000..146d9fe --- /dev/null +++ b/packages/strapi/src/api/issue/routes/issue.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * issue router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +module.exports = createCoreRouter('api::issue.issue'); diff --git a/packages/strapi/src/api/issue/services/issue.js b/packages/strapi/src/api/issue/services/issue.js new file mode 100644 index 0000000..9782a38 --- /dev/null +++ b/packages/strapi/src/api/issue/services/issue.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * issue service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::issue.issue'); diff --git a/packages/strapi/src/api/mux-asset/content-types/mux-asset/schema.json b/packages/strapi/src/api/mux-asset/content-types/mux-asset/schema.json new file mode 100644 index 0000000..736ba34 --- /dev/null +++ b/packages/strapi/src/api/mux-asset/content-types/mux-asset/schema.json @@ -0,0 +1,28 @@ +{ + "kind": "collectionType", + "collectionName": "mux_assets", + "info": { + "singularName": "mux-asset", + "pluralName": "mux-assets", + "displayName": "Mux Asset", + "description": "" + }, + "options": { + "draftAndPublish": false + }, + "pluginOptions": {}, + "attributes": { + "playbackId": { + "type": "string", + "required": false, + "unique": true + }, + "assetId": { + "type": "string", + "unique": true + }, + "deletionQueuedAt": { + "type": "datetime" + } + } +} diff --git a/packages/strapi/src/api/mux-asset/controllers/mux-asset.js b/packages/strapi/src/api/mux-asset/controllers/mux-asset.js new file mode 100644 index 0000000..e02adbc --- /dev/null +++ b/packages/strapi/src/api/mux-asset/controllers/mux-asset.js @@ -0,0 +1,56 @@ +'use strict'; + +const { JWT } = require('@mux/mux-node'); + +const MUX_SIGNING_KEY_ID = process.env.MUX_SIGNING_KEY_ID; +const MUX_SIGNING_KEY_PRIVATE_KEY = process.env.MUX_SIGNING_KEY_PRIVATE_KEY; +const MUX_PLAYBACK_RESTRICTION_ID = process.env.MUX_PLAYBACK_RESTRICTION_ID + +if (!MUX_SIGNING_KEY_PRIVATE_KEY) throw new Error('MUX_SIGNING_KEY_PRIVATE_KEY must be defined in env'); +if (!MUX_SIGNING_KEY_ID) throw new Error('MUX_SIGNING_KEY_ID must be defined in env'); +if (!MUX_PLAYBACK_RESTRICTION_ID) throw new Error('MUX_PLAYBACK_RESTRICTION_ID must be defined in env'); + + +/** + * mux-asset controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::mux-asset.mux-asset', ({ strapi }) => ({ + + async secure (ctx, next) { + + if (ctx?.query?.id === undefined) { + ctx.throw(400, 'id query param was missing!') + return + } + + const tokens = {} + + tokens.playbackToken = JWT.signPlaybackId(ctx.query.id, { + keyId: MUX_SIGNING_KEY_ID, + keySecret: MUX_SIGNING_KEY_PRIVATE_KEY, + params: { + playback_restriction_id: MUX_PLAYBACK_RESTRICTION_ID + }, + }) + + + tokens.storyboardToken = JWT.signPlaybackId(ctx.query.id, { + keyId: MUX_SIGNING_KEY_ID, + keySecret: MUX_SIGNING_KEY_PRIVATE_KEY, + type: 'storyboard' + }) + + tokens.thumbnailToken = JWT.signPlaybackId(ctx.query.id, { + keyId: MUX_SIGNING_KEY_ID, + keySecret: MUX_SIGNING_KEY_PRIVATE_KEY, + type: 'thumbnail' + }) + + + ctx.body = tokens + } +})) + diff --git a/packages/strapi/src/api/mux-asset/routes/mux-asset.js b/packages/strapi/src/api/mux-asset/routes/mux-asset.js new file mode 100644 index 0000000..2dc38eb --- /dev/null +++ b/packages/strapi/src/api/mux-asset/routes/mux-asset.js @@ -0,0 +1,33 @@ +'use strict'; + +/** + * mux-asset router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +const defaultRouter = createCoreRouter('api::mux-asset.mux-asset'); + +// greets https://forum.strapi.io/t/how-to-add-custom-routes-to-core-routes-in-strapi-4/14070/7 +const customRouter = (innerRouter, extraRoutes = []) => { + let routes; + return { + get prefix() { + return innerRouter.prefix; + }, + get routes() { + if (!routes) routes = extraRoutes.concat(innerRouter.routes) + return routes; + }, + }; +}; + +const myExtraRoutes = [ + { + method: "GET", + path: "/mux-asset/secure", + handler: "mux-asset.secure" + } +]; + +module.exports = customRouter(defaultRouter, myExtraRoutes); \ No newline at end of file diff --git a/packages/strapi/src/api/mux-asset/services/mux-asset.js b/packages/strapi/src/api/mux-asset/services/mux-asset.js new file mode 100644 index 0000000..a4cbcb5 --- /dev/null +++ b/packages/strapi/src/api/mux-asset/services/mux-asset.js @@ -0,0 +1,78 @@ +'use strict'; + +const { JWT } = require('@mux/mux-node'); +const { createCoreService } = require('@strapi/strapi').factories; + +/** + * mux-asset service + */ + + +module.exports = createCoreService('api::mux-asset.mux-asset', ({strapi}) => ({ + + + /** + * reference: https://docs.mux.com/guides/video/secure-video-playback#4-generate-a-json-web-token-jwt + * reference: https://docs.mux.com/guides/video/secure-video-playback#5-sign-the-json-web-token-jwt + * + * @param {String} playbackId - signed playback ID + * @param {String} keyId - signing key ID + * @param {String} keySecret - base64 encoded private key + * @param {String} playbackRestructionId - https://docs.mux.com/guides/video/secure-video-playback#create-a-playback-restriction + * @returns {Object} jwt - + * @returns {String} jwt.token - + * @returns {String} jwt.gifToken - + * @returns {String} jwt.thumbnailToken - + */ + async signJwt (playbackId, keyId, keySecret, playbackRestrictionId) { + // Set some base options we can use for a few different signing types + // Type can be either video, thumbnail, gif, or storyboard + let baseOptions = { + keyId: keyId, // Enter your signing key id here + keySecret: keySecret, // Enter your base64 encoded private key here + expiration: '7d' // E.g 60, "2 days", "10h", "7d", numeric value interpreted as seconds + }; + + const playbackToken = JWT.signPlaybackId(playbackId, { + ...baseOptions , + type: 'video', + params: { playback_restriction_id: playbackRestrictionId } + }); + + // Now the signed playback url should look like this: + // https://stream.mux.com/${playbackId}.m3u8?token=${token} + + // If you wanted to pass in params for something like a gif, use the + // params key in the options object + // const gifToken = JWT.signPlaybackId(playbackId, { + // ...baseOptions, + // type: 'gif', + // params: { time: 10 }, + // }) + + const thumbnailToken = JWT.signPlaybackId(playbackId, { + type: 'thumbnail', + params: { playback_restriction_id: playbackRestrictionId }, + }) + + // Then, use this token in a URL like this: + // https://image.mux.com/${playbackId}/animated.gif?token=${gifToken} + + // A final example, if you wanted to sign a thumbnail url with a playback restriction + const storyboardToken = JWT.sign(playbackId, { + ...baseOptions, + type: 'storyboard', + params: { playback_restriction_id: playbackRestrictionId }, + }) + + // When used in a URL, it should look like this: + // https://image.mux.com/${playbackId}/thumbnail.png?token=${thumbnailToken} + + return { + playbackToken, + storyboardToken, + thumbnailToken + } + }, + +})); \ No newline at end of file diff --git a/packages/strapi/src/api/patreon/content-types/patreon/schema.json b/packages/strapi/src/api/patreon/content-types/patreon/schema.json new file mode 100644 index 0000000..a76213c --- /dev/null +++ b/packages/strapi/src/api/patreon/content-types/patreon/schema.json @@ -0,0 +1,39 @@ +{ + "kind": "singleType", + "collectionName": "patreons", + "info": { + "singularName": "patreon", + "pluralName": "patreons", + "displayName": "Patreon", + "description": "" + }, + "options": { + "draftAndPublish": false + }, + "pluginOptions": {}, + "attributes": { + "benefitId": { + "type": "string", + "required": true, + "default": "4760169" + }, + "accessToken": { + "type": "string" + }, + "refreshToken": { + "type": "string" + }, + "campaignId": { + "type": "string", + "required": true + }, + "muxAllocationCostCents": { + "type": "integer", + "default": 50, + "required": true + }, + "membershipId": { + "type": "string" + } + } +} diff --git a/packages/strapi/src/api/patreon/controllers/patreon.js b/packages/strapi/src/api/patreon/controllers/patreon.js new file mode 100644 index 0000000..c06944a --- /dev/null +++ b/packages/strapi/src/api/patreon/controllers/patreon.js @@ -0,0 +1,37 @@ +'use strict'; + +/** + * patreon controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::patreon.patreon', ({ strapi }) => ({ + async getPublicPatrons(ctx) { + const patrons = await strapi.entityService.findMany('plugin::users-permissions.user', { + fields: ['username', 'vanityLink', 'isNamePublic', 'isLinkPublic', 'patreonBenefits'], + }) + + let publicPatrons = [] + for (const patron of patrons) { + let publicPatron = {} + let benefits = (!!patron?.patreonBenefits) ? patron.patreonBenefits.split(',') : [] + if (patron.isNamePublic) publicPatron.username = patron.username; + + // if patron has "Your URL displayed on Futureporn.net" benefit, + // publically share their link if they want it shared + if (benefits.includes('10663202')) { + if (patron.isLinkPublic) publicPatron.vanityLink = patron.vanityLink; + } + + if (!!publicPatron.username || !!publicPatron.vanityLink) publicPatrons.push(publicPatron); + } + + return publicPatrons + }, + async muxAllocationCount(ctx) { + const count = await strapi.service('api::patreon.patreon').getMuxAllocationCount() + return count + } +})); + diff --git a/packages/strapi/src/api/patreon/routes/patreon.js b/packages/strapi/src/api/patreon/routes/patreon.js new file mode 100644 index 0000000..52af951 --- /dev/null +++ b/packages/strapi/src/api/patreon/routes/patreon.js @@ -0,0 +1,37 @@ +'use strict'; + +/** + * patreon router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +const defaultRouter = createCoreRouter('api::patreon.patreon'); + +// greets https://forum.strapi.io/t/how-to-add-custom-routes-to-core-routes-in-strapi-4/14070/7 +const customRouter = (innerRouter, extraRoutes = []) => { + let routes; + return { + get prefix() { + return innerRouter.prefix; + }, + get routes() { + if (!routes) routes = extraRoutes.concat(innerRouter.routes) + return routes; + }, + }; +}; + +const myExtraRoutes = [ + { + method: "GET", + path: "/patreon/patrons", + handler: "api::patreon.patreon.getPublicPatrons" + }, { + method: 'GET', + path: '/patreon/muxAllocationCount', + handler: 'api::patreon.patreon.muxAllocationCount' + } +]; + +module.exports = customRouter(defaultRouter, myExtraRoutes); \ No newline at end of file diff --git a/packages/strapi/src/api/patreon/services/patreon.js b/packages/strapi/src/api/patreon/services/patreon.js new file mode 100644 index 0000000..147d2a4 --- /dev/null +++ b/packages/strapi/src/api/patreon/services/patreon.js @@ -0,0 +1,45 @@ +'use strict'; + +/** + * patreon service + */ + +const EleventyFetch = require("@11ty/eleventy-fetch"); +const { createCoreService } = require('@strapi/strapi').factories; + + + +module.exports = createCoreService('api::patreon.patreon', ({strapi}) => ({ + + + async getPatreonCampaign() { + return EleventyFetch('https://www.patreon.com/api/campaigns/8012692', { + duration: "12h", + type: "json", + }) + }, + + + async getPatreonCampaignPledgeSum() { + const campaign = await this.getPatreonCampaign() + return campaign.data.attributes.pledge_sum + }, + + + /** + * Calculate how many mux allocations the site should have, based on the dollar amount of pledges from patreon + * + * @param {Number} pledgeSum - USD cents + */ + async getMuxAllocationCount() { + const patreonData = await strapi.entityService.findMany('api::patreon.patreon', { + fields: ['muxAllocationCostCents'] + }) + if (!patreonData) throw new Error('patreonData in Strapi was missing'); + const muxAllocationCostCents = patreonData.muxAllocationCostCents + const pledgeSum = await this.getPatreonCampaignPledgeSum() + const muxAllocationCount = Math.floor(pledgeSum / muxAllocationCostCents); // calculate the number of mux allocations required + return muxAllocationCount; + } +})); + diff --git a/packages/strapi/src/api/profile/controllers/profile.js b/packages/strapi/src/api/profile/controllers/profile.js new file mode 100644 index 0000000..610a88d --- /dev/null +++ b/packages/strapi/src/api/profile/controllers/profile.js @@ -0,0 +1,21 @@ +'use strict'; + + + +module.exports = { + update: async (ctx, next) => { + const update = strapi.plugin('users-permissions').controllers.user.update + await update(ctx); + }, + me: async (ctx) => { + const userId = ctx?.state?.user?.id; + if (!userId) return ctx.badRequest("There was no user id in the request!"); + const user = await strapi.entityService.findOne('plugin::users-permissions.user', userId, { + populate: 'role' + }); + return user + }, + test: async (ctx) => { + return 'blah' + } +}; diff --git a/packages/strapi/src/api/profile/routes/profile.js b/packages/strapi/src/api/profile/routes/profile.js new file mode 100644 index 0000000..7a33f1c --- /dev/null +++ b/packages/strapi/src/api/profile/routes/profile.js @@ -0,0 +1,23 @@ +module.exports = { + routes: [ + { + method: 'PUT', + path: '/profile/:id', + handler: 'profile.update', + config: { + prefix: '', + policies: ['global::updateOwnerOnly'] + }, + }, + { + method: 'GET', + path: '/profile/me', + handler: 'profile.me' + }, + { + method: 'GET', + path: '/profile/test', + handler: 'profile.test' + } + ], +}; diff --git a/packages/strapi/src/api/profile/services/profile.js b/packages/strapi/src/api/profile/services/profile.js new file mode 100644 index 0000000..56ac091 --- /dev/null +++ b/packages/strapi/src/api/profile/services/profile.js @@ -0,0 +1,7 @@ +'use strict'; + +/** + * profile service + */ + +module.exports = () => ({}); diff --git a/packages/strapi/src/api/stream/content-types/stream/lifecycles.js b/packages/strapi/src/api/stream/content-types/stream/lifecycles.js new file mode 100644 index 0000000..661c14e --- /dev/null +++ b/packages/strapi/src/api/stream/content-types/stream/lifecycles.js @@ -0,0 +1,61 @@ +const { init } = require('@paralleldrive/cuid2'); + + + +module.exports = { + async beforeUpdate(event) { + const { data } = event.params; + if (!data.cuid) { + const length = 10; // 50% odds of collision after ~51,386,368 ids + const cuid = init({ length }); + event.params.data.cuid = cuid(); + } + }, + async afterUpdate(event) { + console.log(`>>>>>>>>>>>>>> STREAM is afterUpdate !!!!!!!!!!!!`); + + const { data, where, select, populate } = event.params; + + console.log(data); + + const id = where.id; + + // greets https://forum.strapi.io/t/how-to-get-previous-component-data-in-lifecycle-hook/25892/4?u=ggr247 + const existingData = await strapi.entityService.findOne("api::stream.stream", id, { + populate: ['vods', 'tweet'] + }) + + // Initialize archiveStatus to a default value + let archiveStatus = 'missing'; + + // Iterate through all vods to determine archiveStatus + for (const vod of existingData.vods) { + if (!!vod.videoSrcHash) { + if (!!vod.note) { + // If a vod has both videoSrcHash and note, set archiveStatus to 'issue' + archiveStatus = 'issue'; + break; // No need to check further + } else { + // If a vod has videoSrcHash but no note, set archiveStatus to 'good' + archiveStatus = 'good'; + } + } + } + + // we can't use query engine here, because that would trigger an infinite loop + // where this + // instead we access knex instance + await strapi.db.connection("streams").where({ id: id }).update({ + archive_status: archiveStatus, + }); + + if (!!existingData.tweet) { + await strapi.db.connection("streams").where({ id: id }).update({ + is_chaturbate_stream: existingData.tweet.isChaturbateInvite, + is_fansly_stream: existingData.tweet.isFanslyInvite + }); + } + + + } +}; \ No newline at end of file diff --git a/packages/strapi/src/api/stream/content-types/stream/schema.json b/packages/strapi/src/api/stream/content-types/stream/schema.json new file mode 100644 index 0000000..1506582 --- /dev/null +++ b/packages/strapi/src/api/stream/content-types/stream/schema.json @@ -0,0 +1,73 @@ +{ + "kind": "collectionType", + "collectionName": "streams", + "info": { + "singularName": "stream", + "pluralName": "streams", + "displayName": "Stream", + "description": "" + }, + "options": { + "draftAndPublish": false + }, + "pluginOptions": {}, + "attributes": { + "date_str": { + "type": "string", + "required": true, + "unique": true, + "regex": "\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z)" + }, + "date2": { + "type": "string", + "required": true, + "unique": true, + "regex": "\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z)" + }, + "vods": { + "type": "relation", + "relation": "oneToMany", + "target": "api::vod.vod", + "mappedBy": "stream" + }, + "vtuber": { + "type": "relation", + "relation": "manyToOne", + "target": "api::vtuber.vtuber", + "inversedBy": "streams" + }, + "tweet": { + "type": "relation", + "relation": "oneToOne", + "target": "api::tweet.tweet" + }, + "date": { + "type": "datetime", + "required": true, + "unique": true + }, + "archiveStatus": { + "type": "enumeration", + "enum": [ + "missing", + "issue", + "good" + ], + "required": true, + "default": "missing" + }, + "cuid": { + "type": "string", + "unique": true, + "required": false + }, + "isChaturbateStream": { + "type": "boolean", + "default": false + }, + "isFanslyStream": { + "type": "boolean", + "default": false + } + } +} diff --git a/packages/strapi/src/api/stream/controllers/stream.js b/packages/strapi/src/api/stream/controllers/stream.js new file mode 100644 index 0000000..aaee46e --- /dev/null +++ b/packages/strapi/src/api/stream/controllers/stream.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * stream controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::stream.stream'); diff --git a/packages/strapi/src/api/stream/routes/stream.js b/packages/strapi/src/api/stream/routes/stream.js new file mode 100644 index 0000000..6af6923 --- /dev/null +++ b/packages/strapi/src/api/stream/routes/stream.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * stream router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +module.exports = createCoreRouter('api::stream.stream'); diff --git a/packages/strapi/src/api/stream/services/stream.js b/packages/strapi/src/api/stream/services/stream.js new file mode 100644 index 0000000..b0311da --- /dev/null +++ b/packages/strapi/src/api/stream/services/stream.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * stream service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::stream.stream'); diff --git a/packages/strapi/src/api/tag-vod-relation/content-types/tag-vod-relation/schema.json b/packages/strapi/src/api/tag-vod-relation/content-types/tag-vod-relation/schema.json new file mode 100644 index 0000000..129ef5a --- /dev/null +++ b/packages/strapi/src/api/tag-vod-relation/content-types/tag-vod-relation/schema.json @@ -0,0 +1,39 @@ +{ + "kind": "collectionType", + "collectionName": "tag_vod_relations", + "info": { + "singularName": "tag-vod-relation", + "pluralName": "tag-vod-relations", + "displayName": "Tag Vod Relation", + "description": "" + }, + "options": { + "draftAndPublish": false + }, + "pluginOptions": {}, + "attributes": { + "votes": { + "type": "integer" + }, + "creator": { + "type": "relation", + "relation": "oneToOne", + "target": "plugin::users-permissions.user" + }, + "tag": { + "type": "relation", + "relation": "oneToOne", + "target": "api::tag.tag" + }, + "creatorId": { + "type": "integer", + "required": true + }, + "vod": { + "type": "relation", + "relation": "manyToOne", + "target": "api::vod.vod", + "inversedBy": "tagVodRelations" + } + } +} diff --git a/packages/strapi/src/api/tag-vod-relation/controllers/tag-vod-relation.js b/packages/strapi/src/api/tag-vod-relation/controllers/tag-vod-relation.js new file mode 100644 index 0000000..8face45 --- /dev/null +++ b/packages/strapi/src/api/tag-vod-relation/controllers/tag-vod-relation.js @@ -0,0 +1,222 @@ +'use strict'; + +/** + * tag-vod-relation controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::tag-vod-relation.tag-vod-relation', ({ strapi }) => ({ + async relate(ctx) { + + const userId = ctx?.state?.user?.id; + if (!userId) return ctx.badRequest("There was no user id in the request!"); + if (!ctx.request.body.data) return ctx.badRequest('data was missing from body'); + if (!ctx.request.body.data.tag) return ctx.badRequest('tag was missing from data'); + if (!ctx.request.body.data.vod) return ctx.badRequest('vod was missing from data'); + + const { tag: tagId, vod: vodId } = ctx.request.body.data; + + const tagVodRelation = await strapi.entityService.create('api::tag-vod-relation.tag-vod-relation', { + data: { + vod: vodId, + tag: tagId, + creator: userId, + creatorId: userId, + publishedAt: new Date(), + votes: 2 + } + }) + + return tagVodRelation + }, + async vote(ctx) { + // @todo + }, + + // // greets https://docs.strapi.io/dev-docs/backend-customization/controllers#extending-core-controllers + // // greets https://docs.strapi.io/dev-docs/backend-customization/controllers#adding-a-new-controller + // // Method 2: Wrapping a core action (leaves core logic in place) + // async find(ctx) { + // // // some custom logic here + // // ctx.query = { ...ctx.query, local: 'en' } + + // const userId = ctx?.state?.user?.id; + // if (!userId) return ctx.badRequest("There was no user id in the request!"); + + + + // // Calling the default core action + // const { data, meta } = await super.find(ctx); + + // // add isCreator if the tvr was created by this user + // let dataWithCreator = data.map((d) => { + // if (d.data.attributes.) + // }) + + // // // some more custom logic + // // meta.date = Date.now() + + // return { data, meta }; + // }, + + // greets https://docs.strapi.io/dev-docs/backend-customization/controllers#extending-core-controllers + // greets https://docs.strapi.io/dev-docs/backend-customization/controllers#adding-a-new-controller + // Method 2: Wrapping a core action (leaves core logic in place) + async create(ctx) { + console.log('>> create a tag vod relation') + // only allow unique tag, vod combos + + const { query } = ctx.request; + + const userId = ctx?.state?.user?.id; + if (!userId) return ctx.badRequest("There was no user id in the request!"); + if (!ctx.request.body.data) return ctx.badRequest('data was missing from body'); + if (!ctx.request.body.data.tag) return ctx.badRequest('tag was missing from data'); + if (!ctx.request.body.data.vod) return ctx.badRequest('vod was missing from data'); + + const { tag: tagId, vod: vodId } = ctx.request.body.data; + + console.log(`lets make a combo entityService.findMany`) + const combo = await strapi.entityService.findMany('api::tag-vod-relation.tag-vod-relation', { + populate: ['tag', 'vod'], + filters: { + $and: [{ + tag: { + id: { + $eq: ctx.request.body.data.tag + } + } + }, { + vod: { + id: { + $eq: ctx.request.body.data.vod + } + } + }] + } + }) + + if (combo.length > 0) { + return ctx.badRequest('this vod already has that tag'); + } + + // @todo add votes and creator + ctx.request.body.data.creator = userId + ctx.request.body.data.votes = 2 + + const parseBody = (ctx) => { + if (ctx.is('multipart')) { + return parseMultipartData(ctx); + } + + const { data } = ctx.request.body || {}; + + return { data }; + }; + + + const sanitizedInputData = { + vod: vodId, + tag: tagId, + publishedAt: new Date(), + creator: userId, + creatorId: userId, + votes: 2 + } + + + + + + const entity = await strapi + .service('api::tag-vod-relation.tag-vod-relation') + .create({ + ...query, + data: sanitizedInputData, + populate: { vod: true, tag: true } + }); + + const sanitizedEntity = await this.sanitizeOutput(entity, ctx); + + return this.transformResponse({ ...sanitizedEntity }); + }, + + + async tagVod (ctx) { + + // create tag if needed + const userId = ctx?.state?.user?.id; + if (!userId) return ctx.badRequest("There was no user id in the request!"); + if (!ctx.request.body.data) return ctx.badRequest('data was missing from body'); + if (!ctx.request.body.data.tagName) return ctx.badRequest('tagName was missing from data'); + if (!ctx.request.body.data.vodId) return ctx.badRequest('vodId was missing from data'); + + const { tagName, vodId } = ctx.request.body.data; + + + const tag = await strapi.service('api::tag.tag').assertTag(tagName, userId); + + + try { + const tvr = await strapi.service('api::tag-vod-relation.tag-vod-relation').assertTvr(tag.id, vodId, userId); + + + const sanitizedEntity = await this.sanitizeOutput(tvr, ctx); + return this.transformResponse({ ...sanitizedEntity }); + } catch (e) { + console.error(e) + ctx.badRequest('Vod Tag could not be created.') + } + + }, + + async deleteMine (ctx) { + // // some custom logic here + // ctx.query = { ...ctx.query, local: 'en' } + + + + const userId = ctx?.state?.user?.id; + if (!userId) return ctx.badRequest("There was no user id in the request!"); + if (!ctx.request.params.id) return ctx.badRequest('id was missing from params'); + const { id } = ctx.request.params; + + // constraints + // only able to delete tagVodRelation if + // * creator + // * publishedAt isBefore(now-24h) + + + // get the tvr the user wants to delete + const tvrToDelete = await strapi.entityService.findOne('api::tag-vod-relation.tag-vod-relation', id, { + populate: { + tag: true, + vod: true, + creator: true, + } + }) + + if (!tvrToDelete) return ctx.badRequest('Tag to be deleted does not exist.'); + + if (tvrToDelete.creator.id !== userId) + ctx.forbidden('only the creator of the tag can delete it'); + + if ((new Date(tvrToDelete.createdAt).valueOf()+86400000) < new Date().valueOf()) + ctx.forbidden('cannot delete tags older than 24 hours') + + // Calling the default core action + const { data, meta } = await super.delete(ctx); + + // delete the related tag if it has no other vod + // @todo?? or maybe this is handled by lifecycle hook? + + // // some more custom logic + // meta.date = Date.now() + + return { data, meta }; + } + + +})); + diff --git a/packages/strapi/src/api/tag-vod-relation/routes/tag-vod-relation.js b/packages/strapi/src/api/tag-vod-relation/routes/tag-vod-relation.js new file mode 100644 index 0000000..2564f0f --- /dev/null +++ b/packages/strapi/src/api/tag-vod-relation/routes/tag-vod-relation.js @@ -0,0 +1,51 @@ +'use strict'; + +/** + * tag-vod-relation router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +const defaultRouter = createCoreRouter('api::tag-vod-relation.tag-vod-relation'); + +// greets https://forum.strapi.io/t/how-to-add-custom-routes-to-core-routes-in-strapi-4/14070/7 +const customRouter = (innerRouter, extraRoutes = []) => { + let routes; + return { + get prefix() { + return innerRouter.prefix; + }, + get routes() { + if (!routes) routes = extraRoutes.concat(innerRouter.routes) + return routes; + }, + }; +}; + +const myExtraRoutes = [ + { + method: "POST", + path: "/tag-vod-relations/relate", + handler: "api::tag-vod-relation.tag-vod-relation.relate" + }, + // { + // method: 'GET', + // path: '/tag-vod-relations', + // handler: 'api::tag-vod-relation.tag-vod-relation.find' + // }, + { + method: "PUT", + path: "/tag-vod-relations/vote", + handler: "api::tag-vod-relation.tag-vod-relation.vote" + }, { + method: 'POST', + path: '/tag-vod-relations/tag', + handler: 'api::tag-vod-relation.tag-vod-relation.tagVod' + }, { + method: 'DELETE', + path: '/tag-vod-relations/deleteMine/:id', + handler: 'api::tag-vod-relation.tag-vod-relation.deleteMine' + } +]; + +module.exports = customRouter(defaultRouter, myExtraRoutes); \ No newline at end of file diff --git a/packages/strapi/src/api/tag-vod-relation/services/tag-vod-relation.js b/packages/strapi/src/api/tag-vod-relation/services/tag-vod-relation.js new file mode 100644 index 0000000..e958bbc --- /dev/null +++ b/packages/strapi/src/api/tag-vod-relation/services/tag-vod-relation.js @@ -0,0 +1,69 @@ +'use strict'; + +/** + * tag-vod-relation service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::tag-vod-relation.tag-vod-relation', ({ strapi }) => ({ + + + async assertTvr(tagId, vodId, userId) { + + if (!tagId) throw new Error('tagId was missing in request'); + if (!vodId) throw new Error('vodId was missing in request'); + if (!userId) throw new Error('userId was missing in request'); + + + + let existingTvr; + existingTvr = await strapi.entityService + .findMany('api::tag-vod-relation.tag-vod-relation', { + limit: 1, + filters: { + $and: [ + { + tag: { + id: tagId, + }, + }, + { + vod: { + id: vodId + } + } + ] + }, + populate: ['tag', 'vod'] + }) + + + if (existingTvr.length === 0) { + const newTvr = await strapi.entityService.create('api::tag-vod-relation.tag-vod-relation', { + data: { + tag: tagId, + vod: vodId, + creator: userId, + creatorId: userId, + }, + populate: { + tag: true, + vod: true + } + }) + + // trigger data revalidation in next.js server + // fetch(`${nextJsServerUrl}/`) + return newTvr; + } else { + + return existingTvr[0]; + } + + + }, + +})); + + diff --git a/packages/strapi/src/api/tag/content-types/tag/schema.json b/packages/strapi/src/api/tag/content-types/tag/schema.json new file mode 100644 index 0000000..2884619 --- /dev/null +++ b/packages/strapi/src/api/tag/content-types/tag/schema.json @@ -0,0 +1,38 @@ +{ + "kind": "collectionType", + "collectionName": "tags", + "info": { + "singularName": "tag", + "pluralName": "tags", + "displayName": "Tag", + "description": "" + }, + "options": { + "draftAndPublish": true + }, + "pluginOptions": {}, + "attributes": { + "name": { + "type": "string", + "unique": true, + "required": true + }, + "toy": { + "type": "relation", + "relation": "manyToOne", + "target": "api::toy.toy", + "inversedBy": "tags" + }, + "vods": { + "type": "relation", + "relation": "manyToMany", + "target": "api::vod.vod", + "inversedBy": "tags" + }, + "creator": { + "type": "relation", + "relation": "oneToOne", + "target": "plugin::users-permissions.user" + } + } +} diff --git a/packages/strapi/src/api/tag/controllers/tag.js b/packages/strapi/src/api/tag/controllers/tag.js new file mode 100644 index 0000000..51669a9 --- /dev/null +++ b/packages/strapi/src/api/tag/controllers/tag.js @@ -0,0 +1,87 @@ +'use strict'; + +/** + * tag controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; +const { sanitize } = require('@strapi/utils'); + + +module.exports = createCoreController('api::tag.tag', ({ strapi }) => ({ + + async random(ctx) { + const numberOfTags = 10; // Change this number to the desired number of random tags + const contentType = strapi.contentType('api::vod.vod'); + + // Fetch only the 'id' field of all tags + const tagIds = (await strapi.entityService.findMany( + "api::tag.tag", + { + fields: ['id'], + } + )).map(tag => tag.id); + + + const selectedTags = []; + + // Randomly select the specified number of tag IDs + for (let i = 0; i < numberOfTags; i++) { + const randomIndex = Math.floor(Math.random() * tagIds.length); + const randomTagId = tagIds[randomIndex]; + + + // Fetch the full details of the randomly selected tag using its ID + const rawTag = await strapi.entityService.findOne( + "api::tag.tag", + randomTagId, // Use the tag's ID + { + filter: { + publishedAt: { + $notNull: true, + }, + }, + fields: ['id', 'name'] + } + ); + + selectedTags.push(await sanitize.contentAPI.output(rawTag, contentType, { auth: ctx.state.auth })); + + // Remove the selected tag ID from the array to avoid duplicates + tagIds.splice(randomIndex, 1); + } + + ctx.body = selectedTags; + }, + + + + async createTagRelation(ctx) { + + // we have this controller which associates a tag with a vod + // this exists so users can indirectly update vod records which they dont have permissions to update + // first we need to get the user's request. + // they are telling us a vod ID and a tag ID + // our job is to get a reference to the vod, and add the tag relation. + + if (!ctx.request.body.data) return ctx.badRequest('data was missing from body'); + if (!ctx.request.body.data.tag) return ctx.badRequest('tag was missing from data'); + if (!ctx.request.body.data.vod) return ctx.badRequest('vod was missing from data'); + + const { tag, vod: vodId } = ctx.request.body.data; + + + + await strapi.entityService.update('api::vod.vod', vodId, { + data: { + tags: { + connect: [tag] + } + } + }) + + return 'OK' + + }, +})); + diff --git a/packages/strapi/src/api/tag/routes/tag.js b/packages/strapi/src/api/tag/routes/tag.js new file mode 100644 index 0000000..db82f8f --- /dev/null +++ b/packages/strapi/src/api/tag/routes/tag.js @@ -0,0 +1,37 @@ +'use strict'; + +/** + * tag router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; +const defaultRouter = createCoreRouter('api::tag.tag'); + +// greets https://forum.strapi.io/t/how-to-add-custom-routes-to-core-routes-in-strapi-4/14070/7 +const customRouter = (innerRouter, extraRoutes = []) => { + let routes; + return { + get prefix() { + return innerRouter.prefix; + }, + get routes() { + if (!routes) routes = extraRoutes.concat(innerRouter.routes) + return routes; + }, + }; +}; + +const myExtraRoutes = [ + { + method: "POST", + path: "/tag/tagRelation", + handler: "api::tag.tag.createTagRelation" + }, + { + method: 'GET', + path: '/tag/random', + handler: 'api::tag.tag.random' + } +]; + +module.exports = customRouter(defaultRouter, myExtraRoutes) diff --git a/packages/strapi/src/api/tag/services/tag.js b/packages/strapi/src/api/tag/services/tag.js new file mode 100644 index 0000000..538bebc --- /dev/null +++ b/packages/strapi/src/api/tag/services/tag.js @@ -0,0 +1,46 @@ +'use strict'; + +/** + * tag service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::tag.tag', ({ strapi }) => ({ + async assertTag(tagName, userId) { + + if (!tagName) throw new Error('tagName was missing from request'); + if (!userId) throw new Error('userId was missing from request'); + + let tagEntry; + + // does the named tag already exist? + // tagEntry = await strapi.db.query('api::tag.tag') + // .findOne({ where: { name: tagName } }); + tagEntry = (await strapi.entityService.findMany('api::tag.tag', { + limit: 1, + filters: { + $and: [ + { + publishedAt: { $notNull: true }, + }, + { + name: tagName + } + ] + } + }))[0] + + if (!tagEntry) { + tagEntry = await strapi.entityService.create('api::tag.tag', { + data: { + name: tagName, + creator: userId, + publishedAt: new Date(), + } + }) + } + + return tagEntry; + }, +})); diff --git a/packages/strapi/src/api/timestamp/content-types/timestamp/schema.json b/packages/strapi/src/api/timestamp/content-types/timestamp/schema.json new file mode 100644 index 0000000..71b8ffb --- /dev/null +++ b/packages/strapi/src/api/timestamp/content-types/timestamp/schema.json @@ -0,0 +1,47 @@ +{ + "kind": "collectionType", + "collectionName": "timestamps", + "info": { + "singularName": "timestamp", + "pluralName": "timestamps", + "displayName": "Timestamp", + "description": "" + }, + "options": { + "draftAndPublish": false, + "populateCreatorFields": true + }, + "pluginOptions": {}, + "attributes": { + "time": { + "type": "integer", + "required": true + }, + "tag": { + "type": "relation", + "relation": "oneToOne", + "target": "api::tag.tag", + "required": true + }, + "vod": { + "type": "relation", + "relation": "manyToOne", + "target": "api::vod.vod", + "inversedBy": "timestamps" + }, + "creatorId": { + "type": "integer", + "required": true + }, + "upvoters": { + "type": "relation", + "relation": "oneToMany", + "target": "plugin::users-permissions.user" + }, + "downvoters": { + "type": "relation", + "relation": "oneToMany", + "target": "plugin::users-permissions.user" + } + } +} diff --git a/packages/strapi/src/api/timestamp/controllers/timestamp.js b/packages/strapi/src/api/timestamp/controllers/timestamp.js new file mode 100644 index 0000000..a131f44 --- /dev/null +++ b/packages/strapi/src/api/timestamp/controllers/timestamp.js @@ -0,0 +1,166 @@ +'use strict'; + +/** + * timestamp controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::timestamp.timestamp', ({ strapi }) => ({ + + + async find(ctx) { + const { data, meta } = await super.find(ctx); + + // Iterate over each timestamp in the data array + const timestampsWithVotes = await Promise.all(data.map(async (timestamp) => { + // Retrieve the upvoters count for the current timestamp + // Retrieve the downvoters count for the current timestamp + const entry = await strapi.db + .query('api::timestamp.timestamp') + .findOne({ + populate: ['upvoters', 'downvoters'], + where: { + id: timestamp.id + } + }); + + const upvotesCount = entry.upvoters.length + const downvotesCount = entry.downvoters.length + + // Create new properties "upvotes" and "downvotes" on the timestamp object + timestamp.attributes.upvotes = upvotesCount; + timestamp.attributes.downvotes = downvotesCount; + + return timestamp; + })); + + + return { data: timestampsWithVotes, meta }; + }, + + + async assert(ctx) { + const userId = ctx?.state?.user?.id; + if (!userId) return ctx.badRequest("There was no user id in the request!"); + if (!ctx.request.body.data) return ctx.badRequest('data was missing from body'); + if (!ctx.request.body.data.tagId) return ctx.badRequest('tagId was missing from data'); + if (ctx.request.body.data.time === undefined || ctx.request.body.data.time === null || ctx.request.body.data.time < 0) return ctx.badRequest('time was missing from data'); + if (!ctx.request.body.data.vodId) return ctx.badRequest('vodId was missing from data'); + const { time, tagId, vodId } = ctx.request.body.data; + const timestamp = await strapi.service('api::timestamp.timestamp').assertTimestamp(userId, tagId, vodId, time); + return timestamp; + }, + + + // greets https://docs.strapi.io/dev-docs/backend-customization/controllers#extending-core-controllers + // greets https://docs.strapi.io/dev-docs/backend-customization/controllers#adding-a-new-controller + // Method 2: Wrapping a core action (leaves core logic in place) + async create(ctx) { + // add creatorId to the record + const userId = ctx?.state?.user?.id; + if (!userId) return ctx.badRequest("There was no user id in the request!"); + if (!ctx.request.body.data) return ctx.badRequest('data was missing from body'); + if (!ctx.request.body.data.tag) return ctx.badRequest('tag was missing from data'); + const { time, tag } = ctx.request.body.data; + + ctx.request.body.data.creatorId = userId + + // does the timestamp already exist with same combination of time+tag? + const duplicate = await strapi.db.query('api::timestamp.timestamp') + .findOne({ where: { time, tag }}) + + if (!!duplicate) return ctx.badRequest('A duplicate timestamp already exists!'); + + // Calling the default core action + const res = await super.create(ctx); + + return res + }, + + + async vote(ctx) { + const userId = ctx?.state?.user?.id; + const { direction } = ctx.request.body.data; + if (!ctx.request.params.id) return ctx.badRequest('id was missing from params'); + const { id } = ctx.request.params; + // get the ts to be voted on + const ts = await strapi.entityService.findOne('api::timestamp.timestamp', id) + if (!ts) return ctx.badRequest('timestamp does not exist'); + const res = await strapi.entityService.update('api::timestamp.timestamp', id, { + data: { + upvoters: direction === 1 ? { connect: [userId] } : { disconnect: [userId] }, + downvoters: direction === 1 ? { disconnect: [userId] } : { connect: [userId] } + } + }); + return res; + }, + + + + async delete(ctx) { + const userId = ctx?.state?.user?.id; + const { id } = ctx.request.params; + + // get the ts to be deleted + const ts = await strapi.entityService.findOne('api::timestamp.timestamp', id) + if (!ts) return ctx.badRequest('Timestamp does not exist') + + + // Refuse to delete if not the tag creator + if (ts.creatorId !== userId) return ctx.forbidden('Only the timestamp creator can delete the timestamp.') + + + const res = await super.delete(ctx) + return res + + }, + + + async deleteMine (ctx) { + // // some custom logic here + // ctx.query = { ...ctx.query, local: 'en' } + + const userId = ctx?.state?.user?.id; + if (!userId) return ctx.badRequest("There was no user id in the request!"); + if (!ctx.request.params.id) return ctx.badRequest('id was missing from params'); + const { id } = ctx.request.params; + + // constraints + // only able to delete tagVodRelation if + // * creator + // * publishedAt isBefore(now-24h) + + + // get the tvr the user wants to delete + const timestampToDelete = await strapi.entityService.findOne('api::timestamp.timestamp', id, { + populate: { + tag: true, + vod: true + } + }) + + if (!timestampToDelete) return ctx.badRequest('Timestamp to be deleted does not exist.'); + + + if (timestampToDelete.creatorId !== userId) + ctx.forbidden('only the creator of the timestamp can delete it'); + + if ((new Date(timestampToDelete.createdAt).valueOf()+86400000) < new Date().valueOf()) + ctx.forbidden('cannot delete tags older than 24 hours') + + // Calling the default core action + const { data, meta } = await super.delete(ctx); + + // delete the related tag if it has no other vod + // @todo?? or maybe this is handled by lifecycle hook? + + // // some more custom logic + // meta.date = Date.now() + + return { data, meta }; + } + + +})) + diff --git a/packages/strapi/src/api/timestamp/routes/timestamp.js b/packages/strapi/src/api/timestamp/routes/timestamp.js new file mode 100644 index 0000000..1c8e7c6 --- /dev/null +++ b/packages/strapi/src/api/timestamp/routes/timestamp.js @@ -0,0 +1,53 @@ +'use strict'; + +/** + * timestamp router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + + +const defaultRouter = createCoreRouter('api::timestamp.timestamp'); + +// greets https://forum.strapi.io/t/how-to-add-custom-routes-to-core-routes-in-strapi-4/14070/7 +const customRouter = (innerRouter, extraRoutes = []) => { + let routes; + return { + get prefix() { + return innerRouter.prefix; + }, + get routes() { + if (!routes) routes = extraRoutes.concat(innerRouter.routes) + return routes; + }, + }; +}; + + + +const myExtraRoutes = [ + { + method: 'POST', + path: '/timestamps/assert', + handler: 'api::timestamp.timestamp.assert' + }, + { + method: "PUT", + path: "/timestamps/:id/vote", + handler: "api::timestamp.timestamp.vote" + }, + { + method: 'DELETE', + path: '/timestamps/:id', + handler: 'api::timestamp.timestamp.delete' + }, + { + method: 'DELETE', + path: '/timestamps/deleteMine/:id', + handler: 'api::timestamp.timestamp.deleteMine' + } +]; + +module.exports = customRouter(defaultRouter, myExtraRoutes); + + diff --git a/packages/strapi/src/api/timestamp/services/timestamp.js b/packages/strapi/src/api/timestamp/services/timestamp.js new file mode 100644 index 0000000..bfa84c2 --- /dev/null +++ b/packages/strapi/src/api/timestamp/services/timestamp.js @@ -0,0 +1,44 @@ +'use strict'; + +/** + * timestamp service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::timestamp.timestamp', ({ strapi }) => ({ + async assertTimestamp(userId, tagId, vodId, time) { + const existingTimestamp = await strapi.entityService.findMany('api::timestamp.timestamp', { + populate: ['vod', 'tag'], + filters: { + $and: [ + { + tag: { + id: tagId + } + }, + { + vod: { + id: vodId + } + }, + { + time: parseInt(time) + } + ] + }, + limit: 1 + }) + if (existingTimestamp.length > 0) return existingTimestamp[0]; + const newTimestamp = await strapi.entityService.create('api::timestamp.timestamp', { + data: { + tag: tagId, + vod: vodId, + creatorId: userId, + time: time, + } + }); + + return newTimestamp; + } +})); diff --git a/packages/strapi/src/api/toy/content-types/toy/schema.json b/packages/strapi/src/api/toy/content-types/toy/schema.json new file mode 100644 index 0000000..4e2baba --- /dev/null +++ b/packages/strapi/src/api/toy/content-types/toy/schema.json @@ -0,0 +1,51 @@ +{ + "kind": "collectionType", + "collectionName": "toys", + "info": { + "singularName": "toy", + "pluralName": "toys", + "displayName": "Toy", + "description": "" + }, + "options": { + "draftAndPublish": false + }, + "pluginOptions": {}, + "attributes": { + "tags": { + "type": "relation", + "relation": "oneToMany", + "target": "api::tag.tag", + "mappedBy": "toy" + }, + "make": { + "type": "string", + "required": true + }, + "model": { + "type": "string", + "required": true + }, + "aspectRatio": { + "type": "string", + "default": "2:1", + "required": true + }, + "image2": { + "type": "string", + "default": "https://futureporn-b2.b-cdn.net/default-thumbnail.webp", + "required": true + }, + "linkTag": { + "type": "relation", + "relation": "oneToOne", + "target": "api::tag.tag" + }, + "vtubers": { + "type": "relation", + "relation": "oneToMany", + "target": "api::vtuber.vtuber", + "mappedBy": "toy" + } + } +} diff --git a/packages/strapi/src/api/toy/controllers/toy.js b/packages/strapi/src/api/toy/controllers/toy.js new file mode 100644 index 0000000..32bae69 --- /dev/null +++ b/packages/strapi/src/api/toy/controllers/toy.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * toy controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::toy.toy'); diff --git a/packages/strapi/src/api/toy/routes/toy.js b/packages/strapi/src/api/toy/routes/toy.js new file mode 100644 index 0000000..21d9d6a --- /dev/null +++ b/packages/strapi/src/api/toy/routes/toy.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * toy router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +module.exports = createCoreRouter('api::toy.toy'); diff --git a/packages/strapi/src/api/toy/services/toy.js b/packages/strapi/src/api/toy/services/toy.js new file mode 100644 index 0000000..cd2dd6d --- /dev/null +++ b/packages/strapi/src/api/toy/services/toy.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * toy service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::toy.toy'); diff --git a/packages/strapi/src/api/tweet/content-types/tweet/lifecycles.js b/packages/strapi/src/api/tweet/content-types/tweet/lifecycles.js new file mode 100644 index 0000000..b92154a --- /dev/null +++ b/packages/strapi/src/api/tweet/content-types/tweet/lifecycles.js @@ -0,0 +1,136 @@ +const generateCuid = require('../../../../../misc/generateCuid.js'); + +const cbUrlRegex = /chaturbate\.com/i; +const fanslyUrlRegex = /https?:\/\/(?:www\.)?fans(?:\.ly|ly\.com)\/r\/[a-zA-Z0-9_]+/; + +const cbAlternativeUrls = [ + 'shorturl.at/tNUVY' // used by ProjektMelody in the early days +] + + +/** + * Returns true if the tweet contains a chaturbate.com link + * + * @param {Object} tweet + * @returns {Boolean} + */ +const containsCBInviteLink = (tweet) => { + const containsCbUrl = (link) => { + if (!link?.url) return false; + const isCbUrl = cbUrlRegex.test(link.url); + const isAlternativeCbUrl = cbAlternativeUrls.some(alternativeUrl => link.url.includes(alternativeUrl)); + return isCbUrl || isAlternativeCbUrl; + } + try { + if (!tweet?.links) return false; + return tweet.links.some(containsCbUrl) + } catch (e) { + logger.log({ level: 'error', message: 'ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR' }); + logger.log({ level: 'error', message: e }); + return false; + } +}; + +const containsFanslyInviteLink = (tweet) => { + const containsFanslyUrl = (link) => { + if (!link?.url) return false; + return (fanslyUrlRegex.test(link?.url)) + } + try { + if (!tweet?.links) return false; + return tweet.links.some(containsFanslyUrl) + } catch (e) { + logger.log({ level: 'error', message: 'ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR' }); + logger.log({ level: 'error', message: e }); + return false; + } +}; + + +const deriveTitle = (text) => { + // greetz https://www.urlregex.com/ + const urlRegex = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/g; + let title = text + .replace(urlRegex, '') // remove urls + .replace(/\n/g, ' ') // replace newlines with spaces + .replace(/>/g, '>') // gimme dem greater-than brackets + .replace(/</g, '<') // i want them less-thans too + .replace(/&/g, '&') // ampersands are sexy + .replace(/\s+$/, ''); // remove trailing whitespace + return title; +}; + + + + +module.exports = { + async afterCreate(event) { + // * [ ] Create Stream + const id = event.result.id; + console.log(`>>> tweet afterCreate id=${id}`); + const { data } = event.params; + + console.log(data); + + // IF this tweet was a fansly or chaturbate invite, create & associate Stream + if (data.isChaturbateInvite || data.isFanslyInvite) { + const stream = await strapi.entityService.create('api::stream.stream', { + data: { + tweet: id, + vtuber: data.vtuber, + date: data.date, + date_str: data.date, + date2: data.date, + archiveStatus: 'missing', + cuid: generateCuid() + } + }); + + // console.log(data) + console.log(`stream.id=${stream.id}`); + + // const existingData = await strapi.entityService.findOne("api::stream.stream", id, { + // populate: ['vods'] + // }) + } + }, + async beforeCreate(event) { + // * [x] Set platform to CB or Fansly + // * [x] Set vtuber + // * [x] Set date + // * [x] Set id_str + // * [x] Set url + + const { data, where, select, populate } = event.params; + console.log('>>> tweet beforeCreate!'); + + const tweet = JSON.parse(data.json); + // console.log(tweet); + console.log(`containsCBInviteLink=${containsCBInviteLink(tweet)}, containsFanslyInviteLink=${containsFanslyInviteLink(tweet)}`); + + + data.isChaturbateInvite = containsCBInviteLink(tweet); + data.isFanslyInvite = containsFanslyInviteLink(tweet); + + const tweetDate = new Date(tweet.date).toISOString(); + data.id_str = tweet.id_str; + data.date = tweetDate; + data.date2 = tweetDate; + data.url = tweet.url; + + // Set VTuber + const twitterUsername = tweet.user.username; + const vtuberRecords = await strapi.entityService.findMany("api::vtuber.vtuber", { + fields: ['displayName', 'slug', 'id'], + filters: { + twitter: { + $endsWithi: twitterUsername + } + } + }); + if (!!vtuberRecords[0]) data.vtuber = vtuberRecords[0].id; + + + + } +} \ No newline at end of file diff --git a/packages/strapi/src/api/tweet/content-types/tweet/schema.json b/packages/strapi/src/api/tweet/content-types/tweet/schema.json new file mode 100644 index 0000000..3d94b7c --- /dev/null +++ b/packages/strapi/src/api/tweet/content-types/tweet/schema.json @@ -0,0 +1,49 @@ +{ + "kind": "collectionType", + "collectionName": "tweets", + "info": { + "singularName": "tweet", + "pluralName": "tweets", + "displayName": "Tweet", + "description": "" + }, + "options": { + "draftAndPublish": false + }, + "pluginOptions": {}, + "attributes": { + "id_str": { + "type": "string", + "required": false, + "unique": true + }, + "url": { + "type": "string", + "required": false + }, + "date2": { + "type": "string", + "required": false, + "unique": true + }, + "json": { + "type": "text", + "required": true + }, + "vtuber": { + "type": "relation", + "relation": "oneToOne", + "target": "api::vtuber.vtuber" + }, + "isChaturbateInvite": { + "type": "boolean" + }, + "isFanslyInvite": { + "type": "boolean" + }, + "date": { + "type": "datetime", + "unique": true + } + } +} diff --git a/packages/strapi/src/api/tweet/controllers/tweet.js b/packages/strapi/src/api/tweet/controllers/tweet.js new file mode 100644 index 0000000..bbb1fed --- /dev/null +++ b/packages/strapi/src/api/tweet/controllers/tweet.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * tweet controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::tweet.tweet'); diff --git a/packages/strapi/src/api/tweet/routes/tweet.js b/packages/strapi/src/api/tweet/routes/tweet.js new file mode 100644 index 0000000..efce886 --- /dev/null +++ b/packages/strapi/src/api/tweet/routes/tweet.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * tweet router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +module.exports = createCoreRouter('api::tweet.tweet'); diff --git a/packages/strapi/src/api/tweet/services/tweet.js b/packages/strapi/src/api/tweet/services/tweet.js new file mode 100644 index 0000000..9be1baf --- /dev/null +++ b/packages/strapi/src/api/tweet/services/tweet.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * tweet service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::tweet.tweet'); diff --git a/packages/strapi/src/api/user-submitted-content/content-types/user-submitted-content/lifecycles.js b/packages/strapi/src/api/user-submitted-content/content-types/user-submitted-content/lifecycles.js new file mode 100644 index 0000000..b9bab96 --- /dev/null +++ b/packages/strapi/src/api/user-submitted-content/content-types/user-submitted-content/lifecycles.js @@ -0,0 +1,58 @@ +const { S3Client, DeleteObjectCommand } = require("@aws-sdk/client-s3"); + +if (!process.env.S3_USC_BUCKET_NAME) throw new Error('S3_USC_BUCKET_NAME must be defined in env'); +if (!process.env.S3_USC_BUCKET_ENDPOINT) throw new Error('S3_USC_BUCKET_ENDPOINT must be defined in env'); +if (!process.env.S3_USC_BUCKET_REGION) throw new Error('S3_USC_BUCKET_REGION must be defined in env'); +if (!process.env.AWS_ACCESS_KEY_ID) throw new Error('AWS_ACCESS_KEY_ID must be defined in env'); +if (!process.env.AWS_SECRET_ACCESS_KEY) throw new Error('AWS_SECRET_ACCESS_KEY must be defined in env'); + +// AWS.config.loadFromPath('./credentials-ehl.json'); + + + +module.exports = { + async beforeCreate(event) { + console.log('>>> beforeCreate!'); + + }, + + // when strapi deletes a USC, we delete the related files in the S3 bucket. + async afterDelete(event) { + + console.log('>>> afterDelete'); + console.log(event); + + const { result } = event; + + + + // a client can be shared by different commands. + const client = new S3Client({ + endpoint: process.env.S3_USC_BUCKET_ENDPOINT, + region: process.env.S3_USC_BUCKET_REGION + }); + // https://fp-usc-dev.s3.us-west-000.backblazeb2.com/GEB7_QcaUAAQ29O.jpg + + const res = await client.send(new DeleteObjectCommand({ + Bucket: process.env.S3_USC_BUCKET_NAME, + Key: result.key + })); + + console.log(res); + + + + // var s3 = new S3(); + // var params = { Bucket: process.env.S3_USC_BUCKET, Key: 'your object' }; + + // const res = await s3.deleteObject(params).promise(); + + // console.log('deletion complete.'); + // console.log(res); + + // , function(err, data) { + // if (err) console.log(err, err.stack); // error + // else console.log(); // deleted + // }); + } +} \ No newline at end of file diff --git a/packages/strapi/src/api/user-submitted-content/content-types/user-submitted-content/schema.json b/packages/strapi/src/api/user-submitted-content/content-types/user-submitted-content/schema.json new file mode 100644 index 0000000..dc1eb4a --- /dev/null +++ b/packages/strapi/src/api/user-submitted-content/content-types/user-submitted-content/schema.json @@ -0,0 +1,36 @@ +{ + "kind": "collectionType", + "collectionName": "user_submitted_contents", + "info": { + "singularName": "user-submitted-content", + "pluralName": "user-submitted-contents", + "displayName": "User Submitted Content", + "description": "" + }, + "options": { + "draftAndPublish": false + }, + "pluginOptions": {}, + "attributes": { + "uploader": { + "type": "relation", + "relation": "oneToOne", + "target": "plugin::users-permissions.user" + }, + "attribution": { + "type": "boolean", + "default": false + }, + "date": { + "type": "string", + "required": true + }, + "notes": { + "type": "richtext" + }, + "files": { + "type": "json", + "required": true + } + } +} diff --git a/packages/strapi/src/api/user-submitted-content/controllers/user-submitted-content.js b/packages/strapi/src/api/user-submitted-content/controllers/user-submitted-content.js new file mode 100644 index 0000000..a94c328 --- /dev/null +++ b/packages/strapi/src/api/user-submitted-content/controllers/user-submitted-content.js @@ -0,0 +1,60 @@ + +'use strict'; + + +if (!process.env.CDN_BUCKET_USC_URL) throw new Error('CDN_BUCKET_USC_URL environment variable is required!'); + +/** + * user-submitted-content controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +// greets https://docs.strapi.io/dev-docs/backend-customization/controllers#adding-a-new-controller +module.exports = createCoreController('api::user-submitted-content.user-submitted-content', ({ strapi }) => ({ + + async createFromUppy(ctx) { + try { + // Destructure data from the request body + const { data } = ctx.request.body; + + console.log(data); + + // Check for required fields in the data + const requiredFields = ['files', 'vtuber', 'date', 'notes', 'attribution']; + if (!data) { + return ctx.badRequest('ctx.request.body.data was missing.'); + } + for (const field of requiredFields) { + console.log(`checking field=${field} data[field]=${data[field]}`); + if (data[field] === undefined || data[field] === null) { + return ctx.badRequest(`${field} was missing from request data.`); + } + } + + // Extract relevant data + const { files, vtuber, date, notes, attribution } = data; + const uploader = ctx.state.user.id; + + console.log('Creating user-submitted content'); + const usc = await strapi.entityService.create('api::user-submitted-content.user-submitted-content', { + data: { + uploader, + files: files.map((f) => ({ ...f, cdnUrl: `${process.env.CDN_BUCKET_USC_URL}/${f.key}` })), + vtuber, + date, + notes, + attribution, + } + }); + + return usc; + } catch (error) { + // Handle unexpected errors + console.error(error); + return ctx.badRequest('An error occurred while processing the request'); + } + } + + })); + \ No newline at end of file diff --git a/packages/strapi/src/api/user-submitted-content/routes/user-submitted-content.js b/packages/strapi/src/api/user-submitted-content/routes/user-submitted-content.js new file mode 100644 index 0000000..ed91c49 --- /dev/null +++ b/packages/strapi/src/api/user-submitted-content/routes/user-submitted-content.js @@ -0,0 +1,33 @@ +'use strict'; + +/** + * user-submitted-content router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +const defaultRouter = createCoreRouter('api::user-submitted-content.user-submitted-content'); + +// greets https://forum.strapi.io/t/how-to-add-custom-routes-to-core-routes-in-strapi-4/14070/7 +const customRouter = (innerRouter, extraRoutes = []) => { + let routes; + return { + get prefix() { + return innerRouter.prefix; + }, + get routes() { + if (!routes) routes = extraRoutes.concat(innerRouter.routes) + return routes; + }, + }; +}; + +const myExtraRoutes = [ + { + method: "POST", + path: "/user-submitted-contents/createFromUppy", + handler: "api::user-submitted-content.user-submitted-content.createFromUppy" + } +]; + +module.exports = customRouter(defaultRouter, myExtraRoutes); \ No newline at end of file diff --git a/packages/strapi/src/api/user-submitted-content/services/user-submitted-content.js b/packages/strapi/src/api/user-submitted-content/services/user-submitted-content.js new file mode 100644 index 0000000..9c176aa --- /dev/null +++ b/packages/strapi/src/api/user-submitted-content/services/user-submitted-content.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * user-submitted-content service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::user-submitted-content.user-submitted-content'); diff --git a/packages/strapi/src/api/vod/content-types/lifecycles.js b/packages/strapi/src/api/vod/content-types/lifecycles.js new file mode 100644 index 0000000..5090507 --- /dev/null +++ b/packages/strapi/src/api/vod/content-types/lifecycles.js @@ -0,0 +1,12 @@ +const { init } = require('@paralleldrive/cuid2'); + +module.exports = { + async beforeUpdate(event) { + const { data } = event.params; + if (!data.cuid) { + const length = 10; // 50% odds of collision after ~51,386,368 ids + const cuid = init({ length }); + event.params.data.cuid = cuid(); + } + } +} \ No newline at end of file diff --git a/packages/strapi/src/api/vod/content-types/vod/schema.json b/packages/strapi/src/api/vod/content-types/vod/schema.json new file mode 100644 index 0000000..2446a76 --- /dev/null +++ b/packages/strapi/src/api/vod/content-types/vod/schema.json @@ -0,0 +1,142 @@ +{ + "kind": "collectionType", + "collectionName": "vods", + "info": { + "singularName": "vod", + "pluralName": "vods", + "displayName": "VOD", + "description": "" + }, + "options": { + "draftAndPublish": true + }, + "pluginOptions": {}, + "attributes": { + "videoSrcHash": { + "type": "string", + "regex": "Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,}", + "required": false, + "unique": true + }, + "video720Hash": { + "type": "string", + "unique": true, + "regex": "Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,}" + }, + "video480Hash": { + "type": "string", + "regex": "Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,}", + "unique": true + }, + "video360Hash": { + "type": "string", + "regex": "Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,}", + "unique": true + }, + "video240Hash": { + "type": "string", + "regex": "Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,}", + "unique": true + }, + "thinHash": { + "type": "string", + "regex": "Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,}", + "unique": true + }, + "thiccHash": { + "type": "string", + "regex": "Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,}", + "unique": true + }, + "announceTitle": { + "type": "string" + }, + "announceUrl": { + "type": "string", + "unique": false + }, + "note": { + "type": "text" + }, + "date": { + "type": "datetime" + }, + "date2": { + "type": "string", + "required": true + }, + "spoilers": { + "type": "richtext" + }, + "title": { + "type": "string" + }, + "uploader": { + "type": "relation", + "relation": "oneToOne", + "target": "plugin::users-permissions.user" + }, + "muxAsset": { + "type": "relation", + "relation": "oneToOne", + "target": "api::mux-asset.mux-asset" + }, + "videoSrcB2": { + "type": "relation", + "relation": "oneToOne", + "target": "api::b2-file.b2-file" + }, + "thumbnail": { + "type": "relation", + "relation": "oneToOne", + "target": "api::b2-file.b2-file" + }, + "chatLog": { + "type": "richtext" + }, + "tags": { + "type": "relation", + "relation": "manyToMany", + "target": "api::tag.tag", + "mappedBy": "vods" + }, + "timestamps": { + "type": "relation", + "relation": "oneToMany", + "target": "api::timestamp.timestamp", + "mappedBy": "vod" + }, + "tagVodRelations": { + "type": "relation", + "relation": "oneToMany", + "target": "api::tag-vod-relation.tag-vod-relation", + "mappedBy": "vod" + }, + "vtuber": { + "type": "relation", + "relation": "manyToOne", + "target": "api::vtuber.vtuber", + "inversedBy": "vods" + }, + "stream": { + "type": "relation", + "relation": "manyToOne", + "target": "api::stream.stream", + "inversedBy": "vods" + }, + "archiveStatus": { + "type": "enumeration", + "enum": [ + "missing", + "issue", + "good" + ], + "required": false, + "default": "issue" + }, + "cuid": { + "type": "string", + "unique": true + } + } +} diff --git a/packages/strapi/src/api/vod/controllers/vod.js b/packages/strapi/src/api/vod/controllers/vod.js new file mode 100644 index 0000000..9dc9ff2 --- /dev/null +++ b/packages/strapi/src/api/vod/controllers/vod.js @@ -0,0 +1,85 @@ +'use strict'; + + +const { sanitize } = require('@strapi/utils'); + +if (!process.env.CDN_BUCKET_USC_URL) throw new Error('CDN_BUCKET_USC_URL environment variable is required!'); + +/** + * vod controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +// greets https://docs.strapi.io/dev-docs/backend-customization/controllers#adding-a-new-controller +module.exports = createCoreController('api::vod.vod', ({ strapi }) => ({ + + async createFromUppy(ctx) { + + const uploaderId = ctx.state.user.id; + + if (!ctx.request.body.data) return ctx.badRequest("data was missing in request body"); + if (!ctx.request.body.data.date) return ctx.badRequest("date was missing"); + if (!ctx.request.body.data.b2Key) return ctx.badRequest("b2Key was missing"); + if (!ctx.request.body.data.b2UploadId) return ctx.badRequest("b2UploadId was missing"); + + + const videoSrcB2 = await strapi.entityService.create('api::b2-file.b2-file', { + data: { + url: `https://f000.backblazeb2.com/b2api/v1/b2_download_file_by_id?fileId=${ctx.request.body.data.b2UploadId}`, + key: ctx.request.body.data.b2Key, + uploadId: ctx.request.body.data.b2UploadId, + cdnUrl: `${process.env.CDN_BUCKET_USC_URL}/${ctx.request.body.data.b2Key}` + } + }); + + const vod = await strapi.entityService.create('api::vod.vod', { + data: { + notes: ctx.request.body.data.notes, + date: ctx.request.body.data.date, + videoSrcB2: videoSrcB2.id, + publishedAt: null, + uploader: uploaderId, + } + }); + + return vod; + }, + + // greets https://stackoverflow.com/a/73929966/1004931 + async random(ctx) { + const numberOfEntries = 1; + const contentType = strapi.contentType('api::vod.vod') + + // Fetch only the 'id' field of all VODs + const entries = await strapi.entityService.findMany( + "api::vod.vod", + { + fields: ['id'], + filters: { + publishedAt: { + $notNull: true, + }, + } + } + ); + + // Randomly select one entry + const randomEntry = entries[Math.floor(Math.random() * entries.length)]; + + // Fetch the full details of the randomly selected VOD + const rawVod = await strapi.entityService.findOne( + "api::vod.vod", + randomEntry.id, + { + populate: '*', + }, + ); + + const sanitizedOutput = await sanitize.contentAPI.output(rawVod, contentType, { auth: ctx.state.auth }); + + ctx.body = sanitizedOutput; + } + + }) +) \ No newline at end of file diff --git a/packages/strapi/src/api/vod/routes/vod.js b/packages/strapi/src/api/vod/routes/vod.js new file mode 100644 index 0000000..132dacf --- /dev/null +++ b/packages/strapi/src/api/vod/routes/vod.js @@ -0,0 +1,38 @@ +'use strict'; + +/** + * vod router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +const defaultRouter = createCoreRouter('api::vod.vod'); + +// greets https://forum.strapi.io/t/how-to-add-custom-routes-to-core-routes-in-strapi-4/14070/7 +const customRouter = (innerRouter, extraRoutes = []) => { + let routes; + return { + get prefix() { + return innerRouter.prefix; + }, + get routes() { + if (!routes) routes = extraRoutes.concat(innerRouter.routes) + return routes; + }, + }; +}; + +const myExtraRoutes = [ + { + method: "POST", + path: "/vods/createFromUppy", + handler: "api::vod.vod.createFromUppy" + }, + { + method: "GET", + path: "/vods/random", + handler: "api::vod.vod.random" + } +]; + +module.exports = customRouter(defaultRouter, myExtraRoutes); \ No newline at end of file diff --git a/packages/strapi/src/api/vod/services/vod.js b/packages/strapi/src/api/vod/services/vod.js new file mode 100644 index 0000000..56fcec1 --- /dev/null +++ b/packages/strapi/src/api/vod/services/vod.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * vod service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::vod.vod'); diff --git a/packages/strapi/src/api/vtuber/content-types/vtuber/lifecycles.js b/packages/strapi/src/api/vtuber/content-types/vtuber/lifecycles.js new file mode 100644 index 0000000..2be365a --- /dev/null +++ b/packages/strapi/src/api/vtuber/content-types/vtuber/lifecycles.js @@ -0,0 +1,23 @@ +const { createCanvas } = require('canvas'); + +function hexColorToBase64Image(hexColor) { + const canvas = createCanvas(1, 1); // Create a canvas + const ctx = canvas.getContext('2d'); + // Draw a rectangle filled with the hex color + ctx.fillStyle = hexColor; + ctx.fillRect(0, 0, canvas.width, canvas.height); + // Convert canvas content to base64 encoded image + const base64Image = canvas.toDataURL('image/png'); + return base64Image; +} + + + +module.exports = { + beforeUpdate(event) { + const { data, where, select, populate } = event.params; + const themeColor = event.params.data.themeColor; + const imageBlur = hexColorToBase64Image(themeColor); + event.params.data.imageBlur = imageBlur; + } +}; \ No newline at end of file diff --git a/packages/strapi/src/api/vtuber/content-types/vtuber/schema.json b/packages/strapi/src/api/vtuber/content-types/vtuber/schema.json new file mode 100644 index 0000000..91568cc --- /dev/null +++ b/packages/strapi/src/api/vtuber/content-types/vtuber/schema.json @@ -0,0 +1,118 @@ +{ + "kind": "collectionType", + "collectionName": "vtubers", + "info": { + "singularName": "vtuber", + "pluralName": "vtubers", + "displayName": "Vtuber", + "description": "" + }, + "options": { + "draftAndPublish": true + }, + "pluginOptions": {}, + "attributes": { + "displayName": { + "type": "string", + "required": true + }, + "chaturbate": { + "type": "string" + }, + "twitter": { + "type": "string" + }, + "patreon": { + "type": "string" + }, + "twitch": { + "type": "string" + }, + "tiktok": { + "type": "string" + }, + "onlyfans": { + "type": "string" + }, + "youtube": { + "type": "string" + }, + "linktree": { + "type": "string" + }, + "carrd": { + "type": "string" + }, + "fansly": { + "type": "string" + }, + "pornhub": { + "type": "string" + }, + "discord": { + "type": "string" + }, + "reddit": { + "type": "string" + }, + "throne": { + "type": "string" + }, + "instagram": { + "type": "string" + }, + "facebook": { + "type": "string" + }, + "merch": { + "type": "string" + }, + "slug": { + "type": "string", + "required": true + }, + "vods": { + "type": "relation", + "relation": "oneToMany", + "target": "api::vod.vod", + "mappedBy": "vtuber" + }, + "description1": { + "type": "text", + "required": true + }, + "description2": { + "type": "text" + }, + "image": { + "type": "string", + "required": true + }, + "themeColor": { + "type": "string", + "default": "#353FFF", + "required": true + }, + "imageBlur": { + "type": "string", + "default": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABmJLR0QA/wD/AP+gvaeTAAAADUlEQVQImWMwtf//HwAEkwJzh0T9qwAAAABJRU5ErkJggg==" + }, + "toys": { + "type": "relation", + "relation": "oneToMany", + "target": "api::toy.toy" + }, + "toy": { + "type": "relation", + "relation": "manyToOne", + "target": "api::toy.toy", + "inversedBy": "vtubers" + }, + "streams": { + "type": "relation", + "relation": "oneToMany", + "target": "api::stream.stream", + "mappedBy": "vtuber" + } + } +} diff --git a/packages/strapi/src/api/vtuber/controllers/vtuber.js b/packages/strapi/src/api/vtuber/controllers/vtuber.js new file mode 100644 index 0000000..e13bb5e --- /dev/null +++ b/packages/strapi/src/api/vtuber/controllers/vtuber.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * vtuber controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::vtuber.vtuber'); diff --git a/packages/strapi/src/api/vtuber/routes/vtuber.js b/packages/strapi/src/api/vtuber/routes/vtuber.js new file mode 100644 index 0000000..8936096 --- /dev/null +++ b/packages/strapi/src/api/vtuber/routes/vtuber.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * vtuber router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +module.exports = createCoreRouter('api::vtuber.vtuber'); diff --git a/packages/strapi/src/api/vtuber/services/vtuber.js b/packages/strapi/src/api/vtuber/services/vtuber.js new file mode 100644 index 0000000..c792042 --- /dev/null +++ b/packages/strapi/src/api/vtuber/services/vtuber.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * vtuber service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::vtuber.vtuber'); diff --git a/packages/strapi/src/extensions/.gitkeep b/packages/strapi/src/extensions/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/strapi/src/extensions/users-permissions/.eslintignore b/packages/strapi/src/extensions/users-permissions/.eslintignore new file mode 100644 index 0000000..1723d82 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/.eslintignore @@ -0,0 +1,2 @@ +node_modules/ +.eslintrc.js diff --git a/packages/strapi/src/extensions/users-permissions/.eslintrc.js b/packages/strapi/src/extensions/users-permissions/.eslintrc.js new file mode 100644 index 0000000..a6c2c1e --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/.eslintrc.js @@ -0,0 +1,14 @@ +module.exports = { + root: true, + overrides: [ + { + files: ['admin/**/*'], + extends: ['custom/front'], + }, + { + files: ['**/*'], + excludedFiles: ['admin/**/*'], + extends: ['custom/back'], + }, + ], +}; diff --git a/packages/strapi/src/extensions/users-permissions/LICENSE b/packages/strapi/src/extensions/users-permissions/LICENSE new file mode 100644 index 0000000..638baf8 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2015-present Strapi Solutions SAS + +Portions of the Strapi software are licensed as follows: + +* All software that resides under an "ee/" directory (the “EE Software”), if that directory exists, is licensed under the license defined in "ee/LICENSE". + +* All software outside of the above-mentioned directories or restrictions above is available under the "MIT Expat" license as set forth below. + +MIT Expat License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/strapi/src/extensions/users-permissions/README.md b/packages/strapi/src/extensions/users-permissions/README.md new file mode 100644 index 0000000..af1f65a --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/README.md @@ -0,0 +1 @@ +# Strapi plugin diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/components/BoundRoute/getMethodColor.js b/packages/strapi/src/extensions/users-permissions/admin/src/components/BoundRoute/getMethodColor.js new file mode 100644 index 0000000..1ad903b --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/components/BoundRoute/getMethodColor.js @@ -0,0 +1,41 @@ +const getMethodColor = (verb) => { + switch (verb) { + case 'POST': { + return { + text: 'success600', + border: 'success200', + background: 'success100', + }; + } + case 'GET': { + return { + text: 'secondary600', + border: 'secondary200', + background: 'secondary100', + }; + } + case 'PUT': { + return { + text: 'warning600', + border: 'warning200', + background: 'warning100', + }; + } + case 'DELETE': { + return { + text: 'danger600', + border: 'danger200', + background: 'danger100', + }; + } + default: { + return { + text: 'neutral600', + border: 'neutral200', + background: 'neutral100', + }; + } + } +}; + +export default getMethodColor; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/components/BoundRoute/index.js b/packages/strapi/src/extensions/users-permissions/admin/src/components/BoundRoute/index.js new file mode 100644 index 0000000..7680ff0 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/components/BoundRoute/index.js @@ -0,0 +1,72 @@ +import React from 'react'; + +import { Box, Flex, Typography } from '@strapi/design-system'; +import map from 'lodash/map'; +import tail from 'lodash/tail'; +import PropTypes from 'prop-types'; +import { useIntl } from 'react-intl'; +import styled from 'styled-components'; + +import getMethodColor from './getMethodColor'; + +const MethodBox = styled(Box)` + margin: -1px; + border-radius: ${({ theme }) => theme.spaces[1]} 0 0 ${({ theme }) => theme.spaces[1]}; +`; + +function BoundRoute({ route }) { + const { formatMessage } = useIntl(); + + const { method, handler: title, path } = route; + const formattedRoute = path ? tail(path.split('/')) : []; + const [controller = '', action = ''] = title ? title.split('.') : []; + const colors = getMethodColor(route.method); + + return ( + + + {formatMessage({ + id: 'users-permissions.BoundRoute.title', + defaultMessage: 'Bound route to', + })} +   + {controller} + + .{action} + + + + + + {method} + + + + {map(formattedRoute, (value) => ( + + /{value} + + ))} + + + + ); +} + +BoundRoute.defaultProps = { + route: { + handler: 'Nocontroller.error', + method: 'GET', + path: '/there-is-no-path', + }, +}; + +BoundRoute.propTypes = { + route: PropTypes.shape({ + handler: PropTypes.string, + method: PropTypes.string, + path: PropTypes.string, + }), +}; + +export default BoundRoute; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/components/FormModal/Input/index.js b/packages/strapi/src/extensions/users-permissions/admin/src/components/FormModal/Input/index.js new file mode 100644 index 0000000..e5eaf94 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/components/FormModal/Input/index.js @@ -0,0 +1,123 @@ +/** + * + * Input + * + */ + +import React from 'react'; + +import { TextInput, ToggleInput } from '@strapi/design-system'; +import PropTypes from 'prop-types'; +import { useIntl } from 'react-intl'; + +const Input = ({ + description, + disabled, + intlLabel, + error, + name, + onChange, + placeholder, + providerToEditName, + type, + value, +}) => { + const { formatMessage } = useIntl(); + const inputValue = + name === 'noName' + ? `${window.strapi.backendURL}/api/connect/${providerToEditName}/callback` + : value; + + const label = formatMessage( + { id: intlLabel.id, defaultMessage: intlLabel.defaultMessage }, + { provider: providerToEditName, ...intlLabel.values } + ); + const hint = description + ? formatMessage( + { id: description.id, defaultMessage: description.defaultMessage }, + { provider: providerToEditName, ...description.values } + ) + : ''; + + if (type === 'bool') { + return ( + { + onChange({ target: { name, value: e.target.checked } }); + }} + /> + ); + } + + const formattedPlaceholder = placeholder + ? formatMessage( + { id: placeholder.id, defaultMessage: placeholder.defaultMessage }, + { ...placeholder.values } + ) + : ''; + + const errorMessage = error ? formatMessage({ id: error, defaultMessage: error }) : ''; + + return ( + + ); +}; + +Input.defaultProps = { + description: null, + disabled: false, + error: '', + placeholder: null, + value: '', +}; + +Input.propTypes = { + description: PropTypes.shape({ + id: PropTypes.string.isRequired, + defaultMessage: PropTypes.string.isRequired, + values: PropTypes.object, + }), + disabled: PropTypes.bool, + error: PropTypes.string, + intlLabel: PropTypes.shape({ + id: PropTypes.string.isRequired, + defaultMessage: PropTypes.string.isRequired, + values: PropTypes.object, + }).isRequired, + name: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + placeholder: PropTypes.shape({ + id: PropTypes.string.isRequired, + defaultMessage: PropTypes.string.isRequired, + values: PropTypes.object, + }), + providerToEditName: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + value: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]), +}; + +export default Input; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/components/FormModal/index.js b/packages/strapi/src/extensions/users-permissions/admin/src/components/FormModal/index.js new file mode 100644 index 0000000..83c0592 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/components/FormModal/index.js @@ -0,0 +1,126 @@ +/** + * + * FormModal + * + */ + +import React from 'react'; + +import { + Button, + Flex, + Grid, + GridItem, + ModalBody, + ModalFooter, + ModalHeader, + ModalLayout, +} from '@strapi/design-system'; +import { Breadcrumbs, Crumb } from '@strapi/design-system/v2'; +import { Form } from '@strapi/helper-plugin'; +import { Formik } from 'formik'; +import PropTypes from 'prop-types'; +import { useIntl } from 'react-intl'; + +import Input from './Input'; + +const FormModal = ({ + headerBreadcrumbs, + initialData, + isSubmiting, + layout, + isOpen, + onSubmit, + onToggle, + providerToEditName, +}) => { + const { formatMessage } = useIntl(); + + if (!isOpen) { + return null; + } + + return ( + + + + {headerBreadcrumbs.map((crumb, index, arr) => ( + + {crumb} + + ))} + + + onSubmit(values)} + initialValues={initialData} + validationSchema={layout.schema} + validateOnChange={false} + > + {({ errors, handleChange, values }) => { + return ( +
    + + + + {layout.form.map((row) => { + return row.map((input) => { + return ( + + + + ); + }); + })} + + + + + {formatMessage({ + id: 'app.components.Button.cancel', + defaultMessage: 'Cancel', + })} + + } + endActions={ + + } + /> + + ); + }} +
    +
    + ); +}; + +FormModal.defaultProps = { + initialData: null, + providerToEditName: null, +}; + +FormModal.propTypes = { + headerBreadcrumbs: PropTypes.arrayOf(PropTypes.string).isRequired, + initialData: PropTypes.object, + layout: PropTypes.shape({ + form: PropTypes.arrayOf(PropTypes.array), + schema: PropTypes.object, + }).isRequired, + isOpen: PropTypes.bool.isRequired, + isSubmiting: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onToggle: PropTypes.func.isRequired, + providerToEditName: PropTypes.string, +}; + +export default FormModal; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/PermissionRow/CheckboxWrapper.js b/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/PermissionRow/CheckboxWrapper.js new file mode 100644 index 0000000..e3165e2 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/PermissionRow/CheckboxWrapper.js @@ -0,0 +1,30 @@ +import { Box } from '@strapi/design-system'; +import styled, { css } from 'styled-components'; + +const activeCheckboxWrapperStyles = css` + background: ${(props) => props.theme.colors.primary100}; + svg { + opacity: 1; + } +`; + +const CheckboxWrapper = styled(Box)` + display: flex; + justify-content: space-between; + align-items: center; + + svg { + opacity: 0; + path { + fill: ${(props) => props.theme.colors.primary600}; + } + } + + /* Show active style both on hover and when the action is selected */ + ${(props) => props.isActive && activeCheckboxWrapperStyles} + &:hover { + ${activeCheckboxWrapperStyles} + } +`; + +export default CheckboxWrapper; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/PermissionRow/SubCategory.js b/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/PermissionRow/SubCategory.js new file mode 100644 index 0000000..a9a91bd --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/PermissionRow/SubCategory.js @@ -0,0 +1,131 @@ +import React, { useCallback, useMemo } from 'react'; + +import { + Box, + Checkbox, + Flex, + Typography, + Grid, + GridItem, + VisuallyHidden, +} from '@strapi/design-system'; +import { Cog as CogIcon } from '@strapi/icons'; +import get from 'lodash/get'; +import PropTypes from 'prop-types'; +import { useIntl } from 'react-intl'; +import styled from 'styled-components'; + +import { useUsersPermissions } from '../../../contexts/UsersPermissionsContext'; + +import CheckboxWrapper from './CheckboxWrapper'; + +const Border = styled.div` + flex: 1; + align-self: center; + border-top: 1px solid ${({ theme }) => theme.colors.neutral150}; +`; + +const SubCategory = ({ subCategory }) => { + const { formatMessage } = useIntl(); + const { onChange, onChangeSelectAll, onSelectedAction, selectedAction, modifiedData } = + useUsersPermissions(); + + const currentScopedModifiedData = useMemo(() => { + return get(modifiedData, subCategory.name, {}); + }, [modifiedData, subCategory]); + + const hasAllActionsSelected = useMemo(() => { + return Object.values(currentScopedModifiedData).every((action) => action.enabled === true); + }, [currentScopedModifiedData]); + + const hasSomeActionsSelected = useMemo(() => { + return ( + Object.values(currentScopedModifiedData).some((action) => action.enabled === true) && + !hasAllActionsSelected + ); + }, [currentScopedModifiedData, hasAllActionsSelected]); + + const handleChangeSelectAll = useCallback( + ({ target: { name } }) => { + onChangeSelectAll({ target: { name, value: !hasAllActionsSelected } }); + }, + [hasAllActionsSelected, onChangeSelectAll] + ); + + const isActionSelected = useCallback( + (actionName) => { + return selectedAction === actionName; + }, + [selectedAction] + ); + + return ( + + + + + {subCategory.label} + + + + + + handleChangeSelectAll({ target: { name: subCategory.name, value } }) + } + indeterminate={hasSomeActionsSelected} + > + {formatMessage({ id: 'app.utils.select-all', defaultMessage: 'Select all' })} + + + + + + {subCategory.actions.map((action) => { + const name = `${action.name}.enabled`; + + return ( + + + onChange({ target: { name, value } })} + > + {action.label} + + + + + ); + })} + + + + ); +}; + +SubCategory.propTypes = { + subCategory: PropTypes.object.isRequired, +}; + +export default SubCategory; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/PermissionRow/index.js b/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/PermissionRow/index.js new file mode 100644 index 0000000..08ff3ff --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/PermissionRow/index.js @@ -0,0 +1,55 @@ +import React, { useMemo } from 'react'; + +import { Box } from '@strapi/design-system'; +import sortBy from 'lodash/sortBy'; +import PropTypes from 'prop-types'; + +import SubCategory from './SubCategory'; + +const PermissionRow = ({ name, permissions }) => { + const subCategories = useMemo(() => { + return sortBy( + Object.values(permissions.controllers).reduce((acc, curr, index) => { + const currentName = `${name}.controllers.${Object.keys(permissions.controllers)[index]}`; + const actions = sortBy( + Object.keys(curr).reduce((acc, current) => { + return [ + ...acc, + { + ...curr[current], + label: current, + name: `${currentName}.${current}`, + }, + ]; + }, []), + 'label' + ); + + return [ + ...acc, + { + actions, + label: Object.keys(permissions.controllers)[index], + name: currentName, + }, + ]; + }, []), + 'label' + ); + }, [name, permissions]); + + return ( + + {subCategories.map((subCategory) => ( + + ))} + + ); +}; + +PermissionRow.propTypes = { + name: PropTypes.string.isRequired, + permissions: PropTypes.object.isRequired, +}; + +export default PermissionRow; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/index.js b/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/index.js new file mode 100644 index 0000000..260ae00 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/index.js @@ -0,0 +1,57 @@ +import React, { useReducer } from 'react'; + +import { Accordion, AccordionContent, AccordionToggle, Box, Flex } from '@strapi/design-system'; +import { useIntl } from 'react-intl'; + +import { useUsersPermissions } from '../../contexts/UsersPermissionsContext'; +import formatPluginName from '../../utils/formatPluginName'; + +import init from './init'; +import PermissionRow from './PermissionRow'; +import { initialState, reducer } from './reducer'; + +const Permissions = () => { + const { modifiedData } = useUsersPermissions(); + const { formatMessage } = useIntl(); + const [{ collapses }, dispatch] = useReducer(reducer, initialState, (state) => + init(state, modifiedData) + ); + + const handleToggle = (index) => + dispatch({ + type: 'TOGGLE_COLLAPSE', + index, + }); + + return ( + + {collapses.map((collapse, index) => ( + handleToggle(index)} + key={collapse.name} + variant={index % 2 === 0 ? 'secondary' : undefined} + > + + + + + + + + ))} + + ); +}; + +export default Permissions; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/init.js b/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/init.js new file mode 100644 index 0000000..4125920 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/init.js @@ -0,0 +1,9 @@ +const init = (initialState, permissions) => { + const collapses = Object.keys(permissions) + .sort() + .map((name) => ({ name, isOpen: false })); + + return { ...initialState, collapses }; +}; + +export default init; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/reducer.js b/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/reducer.js new file mode 100644 index 0000000..705e580 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/components/Permissions/reducer.js @@ -0,0 +1,27 @@ +import produce from 'immer'; + +const initialState = { + collapses: [], +}; + +const reducer = (state, action) => + // eslint-disable-next-line consistent-return + produce(state, (draftState) => { + switch (action.type) { + case 'TOGGLE_COLLAPSE': { + draftState.collapses = state.collapses.map((collapse, index) => { + if (index === action.index) { + return { ...collapse, isOpen: !collapse.isOpen }; + } + + return { ...collapse, isOpen: false }; + }); + + break; + } + default: + return draftState; + } + }); + +export { initialState, reducer }; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/components/Policies/index.js b/packages/strapi/src/extensions/users-permissions/admin/src/components/Policies/index.js new file mode 100644 index 0000000..e9c53b3 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/components/Policies/index.js @@ -0,0 +1,62 @@ +import React from 'react'; + +import { Flex, GridItem, Typography } from '@strapi/design-system'; +import get from 'lodash/get'; +import isEmpty from 'lodash/isEmpty'; +import without from 'lodash/without'; +import { useIntl } from 'react-intl'; + +import { useUsersPermissions } from '../../contexts/UsersPermissionsContext'; +import BoundRoute from '../BoundRoute'; + +const Policies = () => { + const { formatMessage } = useIntl(); + const { selectedAction, routes } = useUsersPermissions(); + + const path = without(selectedAction.split('.'), 'controllers'); + const controllerRoutes = get(routes, path[0]); + const pathResolved = path.slice(1).join('.'); + + const displayedRoutes = isEmpty(controllerRoutes) + ? [] + : controllerRoutes.filter((o) => o.handler.endsWith(pathResolved)); + + return ( + + {selectedAction ? ( + + {displayedRoutes.map((route, key) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} + + ) : ( + + + {formatMessage({ + id: 'users-permissions.Policies.header.title', + defaultMessage: 'Advanced settings', + })} + + + {formatMessage({ + id: 'users-permissions.Policies.header.hint', + defaultMessage: + "Select the application's actions or the plugin's actions and click on the cog icon to display the bound route", + })} + + + )} + + ); +}; + +export default Policies; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/components/UsersPermissions/index.js b/packages/strapi/src/extensions/users-permissions/admin/src/components/UsersPermissions/index.js new file mode 100644 index 0000000..6f40fa3 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/components/UsersPermissions/index.js @@ -0,0 +1,95 @@ +import React, { forwardRef, memo, useImperativeHandle, useReducer } from 'react'; + +import { Flex, Grid, GridItem, Typography } from '@strapi/design-system'; +import PropTypes from 'prop-types'; +import { useIntl } from 'react-intl'; + +import { UsersPermissionsProvider } from '../../contexts/UsersPermissionsContext'; +import getTrad from '../../utils/getTrad'; +import Permissions from '../Permissions'; +import Policies from '../Policies'; + +import init from './init'; +import reducer, { initialState } from './reducer'; + +const UsersPermissions = forwardRef(({ permissions, routes }, ref) => { + const { formatMessage } = useIntl(); + const [state, dispatch] = useReducer(reducer, initialState, (state) => + init(state, permissions, routes) + ); + + useImperativeHandle(ref, () => ({ + getPermissions() { + return { + permissions: state.modifiedData, + }; + }, + resetForm() { + dispatch({ type: 'ON_RESET' }); + }, + setFormAfterSubmit() { + dispatch({ type: 'ON_SUBMIT_SUCCEEDED' }); + }, + })); + + const handleChange = ({ target: { name, value } }) => + dispatch({ + type: 'ON_CHANGE', + keys: name.split('.'), + value: value === 'empty__string_value' ? '' : value, + }); + + const handleChangeSelectAll = ({ target: { name, value } }) => + dispatch({ + type: 'ON_CHANGE_SELECT_ALL', + keys: name.split('.'), + value, + }); + + const handleSelectedAction = (actionToSelect) => + dispatch({ + type: 'SELECT_ACTION', + actionToSelect, + }); + + const providerValue = { + ...state, + onChange: handleChange, + onChangeSelectAll: handleChangeSelectAll, + onSelectedAction: handleSelectedAction, + }; + + return ( + + + + + + + {formatMessage({ + id: getTrad('Plugins.header.title'), + defaultMessage: 'Permissions', + })} + + + {formatMessage({ + id: getTrad('Plugins.header.description'), + defaultMessage: 'Only actions bound by a route are listed below.', + })} + + + + + + + + + ); +}); + +UsersPermissions.propTypes = { + permissions: PropTypes.object.isRequired, + routes: PropTypes.object.isRequired, +}; + +export default memo(UsersPermissions); diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/components/UsersPermissions/init.js b/packages/strapi/src/extensions/users-permissions/admin/src/components/UsersPermissions/init.js new file mode 100644 index 0000000..e124042 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/components/UsersPermissions/init.js @@ -0,0 +1,10 @@ +const init = (state, permissions, routes) => { + return { + ...state, + initialData: permissions, + modifiedData: permissions, + routes, + }; +}; + +export default init; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/components/UsersPermissions/reducer.js b/packages/strapi/src/extensions/users-permissions/admin/src/components/UsersPermissions/reducer.js new file mode 100644 index 0000000..8a03a18 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/components/UsersPermissions/reducer.js @@ -0,0 +1,62 @@ +/* eslint-disable consistent-return */ +import produce from 'immer'; +import get from 'lodash/get'; +import set from 'lodash/set'; +import take from 'lodash/take'; + +export const initialState = { + initialData: {}, + modifiedData: {}, + routes: {}, + selectedAction: '', + policies: [], +}; + +const reducer = (state, action) => + produce(state, (draftState) => { + switch (action.type) { + case 'ON_CHANGE': { + const keysLength = action.keys.length; + const isChangingCheckbox = action.keys[keysLength - 1] === 'enabled'; + + if (action.value && isChangingCheckbox) { + const selectedAction = take(action.keys, keysLength - 1).join('.'); + draftState.selectedAction = selectedAction; + } + + set(draftState, ['modifiedData', ...action.keys], action.value); + break; + } + case 'ON_CHANGE_SELECT_ALL': { + const pathToValue = ['modifiedData', ...action.keys]; + const oldValues = get(state, pathToValue, {}); + const updatedValues = Object.keys(oldValues).reduce((acc, current) => { + acc[current] = { ...oldValues[current], enabled: action.value }; + + return acc; + }, {}); + + set(draftState, pathToValue, updatedValues); + + break; + } + case 'ON_RESET': { + draftState.modifiedData = state.initialData; + break; + } + case 'ON_SUBMIT_SUCCEEDED': { + draftState.initialData = state.modifiedData; + break; + } + + case 'SELECT_ACTION': { + const { actionToSelect } = action; + draftState.selectedAction = actionToSelect === state.selectedAction ? '' : actionToSelect; + break; + } + default: + return draftState; + } + }); + +export default reducer; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/contexts/UsersPermissionsContext/index.js b/packages/strapi/src/extensions/users-permissions/admin/src/contexts/UsersPermissionsContext/index.js new file mode 100644 index 0000000..ce66c34 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/contexts/UsersPermissionsContext/index.js @@ -0,0 +1,18 @@ +import React, { createContext, useContext } from 'react'; + +import PropTypes from 'prop-types'; + +const UsersPermissions = createContext({}); + +const UsersPermissionsProvider = ({ children, value }) => { + return {children}; +}; + +const useUsersPermissions = () => useContext(UsersPermissions); + +UsersPermissionsProvider.propTypes = { + children: PropTypes.node.isRequired, + value: PropTypes.object.isRequired, +}; + +export { UsersPermissions, UsersPermissionsProvider, useUsersPermissions }; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/hooks/index.js b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/index.js new file mode 100644 index 0000000..9988733 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/index.js @@ -0,0 +1,5 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as useForm } from './useForm'; +export { default as useRolesList } from './useRolesList'; +export * from './usePlugins'; +export { default as useFetchRole } from './useFetchRole'; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useFetchRole/index.js b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useFetchRole/index.js new file mode 100644 index 0000000..629e001 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useFetchRole/index.js @@ -0,0 +1,67 @@ +import { useCallback, useEffect, useReducer, useRef } from 'react'; + +import { useFetchClient, useNotification } from '@strapi/helper-plugin'; + +import pluginId from '../../pluginId'; + +import reducer, { initialState } from './reducer'; + +const useFetchRole = (id) => { + const [state, dispatch] = useReducer(reducer, initialState); + const toggleNotification = useNotification(); + const isMounted = useRef(null); + const { get } = useFetchClient(); + + useEffect(() => { + isMounted.current = true; + + if (id) { + fetchRole(id); + } else { + dispatch({ + type: 'GET_DATA_SUCCEEDED', + role: {}, + }); + } + + return () => (isMounted.current = false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [id]); + + const fetchRole = async (roleId) => { + try { + const { + data: { role }, + } = await get(`/${pluginId}/roles/${roleId}`); + + // Prevent updating state on an unmounted component + if (isMounted.current) { + dispatch({ + type: 'GET_DATA_SUCCEEDED', + role, + }); + } + } catch (err) { + console.error(err); + + dispatch({ + type: 'GET_DATA_ERROR', + }); + toggleNotification({ + type: 'warning', + message: { id: 'notification.error' }, + }); + } + }; + + const handleSubmitSucceeded = useCallback((data) => { + dispatch({ + type: 'ON_SUBMIT_SUCCEEDED', + ...data, + }); + }, []); + + return { ...state, onSubmitSucceeded: handleSubmitSucceeded }; +}; + +export default useFetchRole; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useFetchRole/reducer.js b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useFetchRole/reducer.js new file mode 100644 index 0000000..99dcf0d --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useFetchRole/reducer.js @@ -0,0 +1,31 @@ +/* eslint-disable consistent-return */ +import produce from 'immer'; + +export const initialState = { + role: {}, + isLoading: true, +}; + +const reducer = (state, action) => + produce(state, (draftState) => { + switch (action.type) { + case 'GET_DATA_SUCCEEDED': { + draftState.role = action.role; + draftState.isLoading = false; + break; + } + case 'GET_DATA_ERROR': { + draftState.isLoading = false; + break; + } + case 'ON_SUBMIT_SUCCEEDED': { + draftState.role.name = action.name; + draftState.role.description = action.description; + break; + } + default: + return draftState; + } + }); + +export default reducer; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useForm/index.js b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useForm/index.js new file mode 100644 index 0000000..0cf0ef6 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useForm/index.js @@ -0,0 +1,70 @@ +import { useCallback, useEffect, useReducer, useRef } from 'react'; + +import { useFetchClient, useNotification, useRBAC } from '@strapi/helper-plugin'; + +import { getRequestURL } from '../../utils'; + +import reducer, { initialState } from './reducer'; + +const useUserForm = (endPoint, permissions) => { + const { isLoading: isLoadingForPermissions, allowedActions } = useRBAC(permissions); + const [{ isLoading, modifiedData }, dispatch] = useReducer(reducer, initialState); + const toggleNotification = useNotification(); + const isMounted = useRef(true); + + const { get } = useFetchClient(); + + useEffect(() => { + const getData = async () => { + try { + dispatch({ + type: 'GET_DATA', + }); + + const { data } = await get(getRequestURL(endPoint)); + + dispatch({ + type: 'GET_DATA_SUCCEEDED', + data, + }); + } catch (err) { + // The user aborted the request + if (isMounted.current) { + dispatch({ + type: 'GET_DATA_ERROR', + }); + console.error(err); + toggleNotification({ + type: 'warning', + message: { id: 'notification.error' }, + }); + } + } + }; + + if (!isLoadingForPermissions) { + getData(); + } + + return () => { + isMounted.current = false; + }; + }, [isLoadingForPermissions, endPoint, get, toggleNotification]); + + const dispatchSubmitSucceeded = useCallback((data) => { + dispatch({ + type: 'ON_SUBMIT_SUCCEEDED', + data, + }); + }, []); + + return { + allowedActions, + dispatchSubmitSucceeded, + isLoading, + isLoadingForPermissions, + modifiedData, + }; +}; + +export default useUserForm; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useForm/reducer.js b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useForm/reducer.js new file mode 100644 index 0000000..1d05786 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useForm/reducer.js @@ -0,0 +1,40 @@ +import produce from 'immer'; + +const initialState = { + isLoading: true, + modifiedData: {}, +}; + +const reducer = (state, action) => + // eslint-disable-next-line consistent-return + produce(state, (draftState) => { + switch (action.type) { + case 'GET_DATA': { + draftState.isLoading = true; + draftState.modifiedData = {}; + + break; + } + case 'GET_DATA_SUCCEEDED': { + draftState.isLoading = false; + draftState.modifiedData = action.data; + + break; + } + case 'GET_DATA_ERROR': { + draftState.isLoading = true; + break; + } + case 'ON_SUBMIT_SUCCEEDED': { + draftState.modifiedData = action.data; + + break; + } + default: { + return draftState; + } + } + }); + +export default reducer; +export { initialState }; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/hooks/usePlugins.js b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/usePlugins.js new file mode 100644 index 0000000..ce1f0e8 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/usePlugins.js @@ -0,0 +1,71 @@ +import { useEffect } from 'react'; + +import { useNotification, useFetchClient, useAPIErrorHandler } from '@strapi/helper-plugin'; +import { useQueries } from 'react-query'; + +import pluginId from '../pluginId'; +import { cleanPermissions, getTrad } from '../utils'; + +export const usePlugins = () => { + const toggleNotification = useNotification(); + const { get } = useFetchClient(); + const { formatAPIError } = useAPIErrorHandler(getTrad); + + const [ + { + data: permissions, + isLoading: isLoadingPermissions, + error: permissionsError, + refetch: refetchPermissions, + }, + { data: routes, isLoading: isLoadingRoutes, error: routesError, refetch: refetchRoutes }, + ] = useQueries([ + { + queryKey: [pluginId, 'permissions'], + async queryFn() { + const res = await get(`/${pluginId}/permissions`); + + return res.data.permissions; + }, + }, + { + queryKey: [pluginId, 'routes'], + async queryFn() { + const res = await get(`/${pluginId}/routes`); + + return res.data.routes; + }, + }, + ]); + + const refetchQueries = async () => { + await Promise.all([refetchPermissions(), refetchRoutes()]); + }; + + useEffect(() => { + if (permissionsError) { + toggleNotification({ + type: 'warning', + message: formatAPIError(permissionsError), + }); + } + }, [toggleNotification, permissionsError, formatAPIError]); + + useEffect(() => { + if (routesError) { + toggleNotification({ + type: 'warning', + message: formatAPIError(routesError), + }); + } + }, [toggleNotification, routesError, formatAPIError]); + + const isLoading = isLoadingPermissions || isLoadingRoutes; + + return { + permissions: permissions ? cleanPermissions(permissions) : {}, + routes: routes ?? {}, + getData: refetchQueries, + isLoading, + }; +}; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useRolesList/index.js b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useRolesList/index.js new file mode 100644 index 0000000..aae9175 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useRolesList/index.js @@ -0,0 +1,65 @@ +import { useCallback, useEffect, useReducer, useRef } from 'react'; + +import { useFetchClient, useNotification } from '@strapi/helper-plugin'; +import get from 'lodash/get'; + +import pluginId from '../../pluginId'; + +import init from './init'; +import reducer, { initialState } from './reducer'; + +const useRolesList = (shouldFetchData = true) => { + const [{ roles, isLoading }, dispatch] = useReducer(reducer, initialState, () => + init(initialState, shouldFetchData) + ); + const toggleNotification = useNotification(); + + const isMounted = useRef(true); + const fetchClient = useFetchClient(); + + const fetchRolesList = useCallback(async () => { + try { + dispatch({ + type: 'GET_DATA', + }); + + const { + data: { roles }, + } = await fetchClient.get(`/${pluginId}/roles`); + + dispatch({ + type: 'GET_DATA_SUCCEEDED', + data: roles, + }); + } catch (err) { + const message = get(err, ['response', 'payload', 'message'], 'An error occured'); + + if (isMounted.current) { + dispatch({ + type: 'GET_DATA_ERROR', + }); + + if (message !== 'Forbidden') { + toggleNotification({ + type: 'warning', + message, + }); + } + } + } + }, [fetchClient, toggleNotification]); + + useEffect(() => { + if (shouldFetchData) { + fetchRolesList(); + } + + return () => { + isMounted.current = false; + }; + }, [shouldFetchData, fetchRolesList]); + + return { roles, isLoading, getData: fetchRolesList }; +}; + +export default useRolesList; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useRolesList/init.js b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useRolesList/init.js new file mode 100644 index 0000000..dfe71d9 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useRolesList/init.js @@ -0,0 +1,5 @@ +const init = (initialState, shouldFetchData) => { + return { ...initialState, isLoading: shouldFetchData }; +}; + +export default init; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useRolesList/reducer.js b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useRolesList/reducer.js new file mode 100644 index 0000000..a6d347b --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/hooks/useRolesList/reducer.js @@ -0,0 +1,31 @@ +/* eslint-disable consistent-return */ +import produce from 'immer'; + +export const initialState = { + roles: [], + isLoading: true, +}; + +const reducer = (state, action) => + produce(state, (draftState) => { + switch (action.type) { + case 'GET_DATA': { + draftState.isLoading = true; + draftState.roles = []; + break; + } + case 'GET_DATA_SUCCEEDED': { + draftState.roles = action.data; + draftState.isLoading = false; + break; + } + case 'GET_DATA_ERROR': { + draftState.isLoading = false; + break; + } + default: + return draftState; + } + }); + +export default reducer; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/index.js b/packages/strapi/src/extensions/users-permissions/admin/src/index.js new file mode 100644 index 0000000..ba721ed --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/index.js @@ -0,0 +1,125 @@ +// NOTE TO PLUGINS DEVELOPERS: +// If you modify this file by adding new options to the plugin entry point +// Here's the file: strapi/docs/3.0.0-beta.x/plugin-development/frontend-field-api.md +// Here's the file: strapi/docs/3.0.0-beta.x/guides/registering-a-field-in-admin.md +// Also the strapi-generate-plugins/files/admin/src/index.js needs to be updated +// IF THE DOC IS NOT UPDATED THE PULL REQUEST WILL NOT BE MERGED +import { prefixPluginTranslations } from '@strapi/helper-plugin'; + +import pluginPkg from '../../package.json'; + +import pluginPermissions from './permissions'; +import pluginId from './pluginId'; +import getTrad from './utils/getTrad'; + +const name = pluginPkg.strapi.name; + +export default { + register(app) { + // Create the plugin's settings section + app.createSettingSection( + { + id: pluginId, + intlLabel: { + id: getTrad('Settings.section-label'), + defaultMessage: 'Users & Permissions plugin', + }, + }, + [ + { + intlLabel: { + id: 'global.roles', + defaultMessage: 'Roles', + }, + id: 'roles', + to: `/settings/${pluginId}/roles`, + async Component() { + const component = await import( + /* webpackChunkName: "users-roles-settings-page" */ './pages/Roles' + ); + + return component; + }, + permissions: pluginPermissions.accessRoles, + }, + { + intlLabel: { + id: getTrad('HeaderNav.link.providers'), + defaultMessage: 'Providers', + }, + id: 'providers', + to: `/settings/${pluginId}/providers`, + async Component() { + const component = await import( + /* webpackChunkName: "users-providers-settings-page" */ './pages/Providers' + ); + + return component; + }, + permissions: pluginPermissions.readProviders, + }, + { + intlLabel: { + id: getTrad('HeaderNav.link.emailTemplates'), + defaultMessage: 'Email templates', + }, + id: 'email-templates', + to: `/settings/${pluginId}/email-templates`, + async Component() { + const component = await import( + /* webpackChunkName: "users-email-settings-page" */ './pages/EmailTemplates' + ); + + return component; + }, + permissions: pluginPermissions.readEmailTemplates, + }, + { + intlLabel: { + id: getTrad('HeaderNav.link.advancedSettings'), + defaultMessage: 'Advanced Settings', + }, + id: 'advanced-settings', + to: `/settings/${pluginId}/advanced-settings`, + async Component() { + const component = await import( + /* webpackChunkName: "users-advanced-settings-page" */ './pages/AdvancedSettings' + ); + + return component; + }, + permissions: pluginPermissions.readAdvancedSettings, + }, + ] + ); + + app.registerPlugin({ + id: pluginId, + name, + }); + }, + bootstrap() {}, + async registerTrads({ locales }) { + const importedTrads = await Promise.all( + locales.map((locale) => { + return import( + /* webpackChunkName: "users-permissions-translation-[request]" */ `./translations/${locale}.json` + ) + .then(({ default: data }) => { + return { + data: prefixPluginTranslations(data, pluginId), + locale, + }; + }) + .catch(() => { + return { + data: {}, + locale, + }; + }); + }) + ); + + return Promise.resolve(importedTrads); + }, +}; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/index.js b/packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/index.js new file mode 100644 index 0000000..3f0726f --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/index.js @@ -0,0 +1,246 @@ +import React, { useMemo } from 'react'; + +import { + Box, + Button, + ContentLayout, + Flex, + Grid, + GridItem, + HeaderLayout, + Main, + Option, + Select, + Typography, + useNotifyAT, +} from '@strapi/design-system'; +import { + CheckPagePermissions, + Form, + GenericInput, + LoadingIndicatorPage, + SettingsPageTitle, + useFocusWhenNavigate, + useNotification, + useOverlayBlocker, + useRBAC, +} from '@strapi/helper-plugin'; +import { Check } from '@strapi/icons'; +import { Formik } from 'formik'; +import { useIntl } from 'react-intl'; +import { useMutation, useQuery, useQueryClient } from 'react-query'; + +import pluginPermissions from '../../permissions'; +import { getTrad } from '../../utils'; + +import { fetchData, putAdvancedSettings } from './utils/api'; +import layout from './utils/layout'; +import schema from './utils/schema'; + +const ProtectedAdvancedSettingsPage = () => ( + + + +); + +const AdvancedSettingsPage = () => { + const { formatMessage } = useIntl(); + const toggleNotification = useNotification(); + const { lockApp, unlockApp } = useOverlayBlocker(); + const { notifyStatus } = useNotifyAT(); + const queryClient = useQueryClient(); + useFocusWhenNavigate(); + + const updatePermissions = useMemo( + () => ({ update: pluginPermissions.updateAdvancedSettings }), + [] + ); + const { + isLoading: isLoadingForPermissions, + allowedActions: { canUpdate }, + } = useRBAC(updatePermissions); + + const { status: isLoadingData, data } = useQuery('advanced', () => fetchData(), { + onSuccess() { + notifyStatus( + formatMessage({ + id: getTrad('Form.advancedSettings.data.loaded'), + defaultMessage: 'Advanced settings data has been loaded', + }) + ); + }, + onError() { + toggleNotification({ + type: 'warning', + message: { id: getTrad('notification.error'), defaultMessage: 'An error occured' }, + }); + }, + }); + + const isLoading = isLoadingForPermissions || isLoadingData !== 'success'; + + const submitMutation = useMutation((body) => putAdvancedSettings(body), { + async onSuccess() { + await queryClient.invalidateQueries('advanced'); + toggleNotification({ + type: 'success', + message: { id: getTrad('notification.success.saved'), defaultMessage: 'Saved' }, + }); + + unlockApp(); + }, + onError() { + toggleNotification({ + type: 'warning', + message: { id: getTrad('notification.error'), defaultMessage: 'An error occured' }, + }); + unlockApp(); + }, + refetchActive: true, + }); + + const { isLoading: isSubmittingForm } = submitMutation; + + const handleSubmit = async (body) => { + lockApp(); + + const urlConfirmation = body.email_confirmation ? body.email_confirmation_redirection : ''; + + await submitMutation.mutateAsync({ ...body, email_confirmation_redirection: urlConfirmation }); + }; + + if (isLoading) { + return ( +
    + + + + + +
    + ); + } + + return ( +
    + + + {({ errors, values, handleChange, isSubmitting }) => { + return ( +
    + } + size="S" + > + {formatMessage({ id: 'global.save', defaultMessage: 'Save' })} + + } + /> + + + + + {formatMessage({ + id: 'global.settings', + defaultMessage: 'Settings', + })} + + + + + + {layout.map((input) => { + let value = values[input.name]; + + if (!value) { + value = input.type === 'bool' ? false : ''; + } + + return ( + + + + ); + })} + + + + + + ); + }} +
    +
    + ); +}; + +export default ProtectedAdvancedSettingsPage; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/utils/api.js b/packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/utils/api.js new file mode 100644 index 0000000..238862a --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/utils/api.js @@ -0,0 +1,18 @@ +import { getFetchClient } from '@strapi/helper-plugin'; + +import { getRequestURL } from '../../../utils'; + +const fetchData = async () => { + const { get } = getFetchClient(); + const { data } = await get(getRequestURL('advanced')); + + return data; +}; + +const putAdvancedSettings = (body) => { + const { put } = getFetchClient(); + + return put(getRequestURL('advanced'), body); +}; + +export { fetchData, putAdvancedSettings }; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/utils/layout.js b/packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/utils/layout.js new file mode 100644 index 0000000..094e5a6 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/utils/layout.js @@ -0,0 +1,96 @@ +import { getTrad } from '../../../utils'; + +const layout = [ + { + intlLabel: { + id: getTrad('EditForm.inputToggle.label.email'), + defaultMessage: 'One account per email address', + }, + description: { + id: getTrad('EditForm.inputToggle.description.email'), + defaultMessage: + 'Disallow the user to create multiple accounts using the same email address with different authentication providers.', + }, + name: 'unique_email', + type: 'bool', + size: { + col: 12, + xs: 12, + }, + }, + { + intlLabel: { + id: getTrad('EditForm.inputToggle.label.sign-up'), + defaultMessage: 'Enable sign-ups', + }, + description: { + id: getTrad('EditForm.inputToggle.description.sign-up'), + defaultMessage: + 'When disabled (OFF), the registration process is forbidden. No one can subscribe anymore no matter the used provider.', + }, + name: 'allow_register', + type: 'bool', + size: { + col: 12, + xs: 12, + }, + }, + { + intlLabel: { + id: getTrad('EditForm.inputToggle.label.email-reset-password'), + defaultMessage: 'Reset password page', + }, + description: { + id: getTrad('EditForm.inputToggle.description.email-reset-password'), + defaultMessage: "URL of your application's reset password page.", + }, + placeholder: { + id: getTrad('EditForm.inputToggle.placeholder.email-reset-password'), + defaultMessage: 'ex: https://youtfrontend.com/reset-password', + }, + name: 'email_reset_password', + type: 'text', + size: { + col: 6, + xs: 12, + }, + }, + { + intlLabel: { + id: getTrad('EditForm.inputToggle.label.email-confirmation'), + defaultMessage: 'Enable email confirmation', + }, + description: { + id: getTrad('EditForm.inputToggle.description.email-confirmation'), + defaultMessage: 'When enabled (ON), new registered users receive a confirmation email.', + }, + name: 'email_confirmation', + type: 'bool', + size: { + col: 12, + xs: 12, + }, + }, + { + intlLabel: { + id: getTrad('EditForm.inputToggle.label.email-confirmation-redirection'), + defaultMessage: 'Redirection url', + }, + description: { + id: getTrad('EditForm.inputToggle.description.email-confirmation-redirection'), + defaultMessage: 'After you confirmed your email, choose where you will be redirected.', + }, + placeholder: { + id: getTrad('EditForm.inputToggle.placeholder.email-confirmation-redirection'), + defaultMessage: 'ex: https://youtfrontend.com/email-confirmation', + }, + name: 'email_confirmation_redirection', + type: 'text', + size: { + col: 6, + xs: 12, + }, + }, +]; + +export default layout; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/utils/schema.js b/packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/utils/schema.js new file mode 100644 index 0000000..b8958a8 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/pages/AdvancedSettings/utils/schema.js @@ -0,0 +1,19 @@ +import { translatedErrors } from '@strapi/helper-plugin'; +import * as yup from 'yup'; + +// eslint-disable-next-line prefer-regex-literals +const URL_REGEX = new RegExp('(^$)|((.+:\\/\\/.*)(d*)\\/?(.*))'); + +const schema = yup.object().shape({ + email_confirmation_redirection: yup.mixed().when('email_confirmation', { + is: true, + then: yup.string().matches(URL_REGEX).required(), + otherwise: yup.string().nullable(), + }), + email_reset_password: yup + .string(translatedErrors.string) + .matches(URL_REGEX, translatedErrors.regex) + .nullable(), +}); + +export default schema; diff --git a/packages/strapi/src/extensions/users-permissions/admin/src/pages/EmailTemplates/components/EmailForm.js b/packages/strapi/src/extensions/users-permissions/admin/src/pages/EmailTemplates/components/EmailForm.js new file mode 100644 index 0000000..5d98122 --- /dev/null +++ b/packages/strapi/src/extensions/users-permissions/admin/src/pages/EmailTemplates/components/EmailForm.js @@ -0,0 +1,176 @@ +import React from 'react'; + +import { + Button, + Grid, + GridItem, + ModalBody, + ModalFooter, + ModalHeader, + ModalLayout, + Textarea, +} from '@strapi/design-system'; +import { Breadcrumbs, Crumb } from '@strapi/design-system/v2'; +import { Form, GenericInput } from '@strapi/helper-plugin'; +import { Formik } from 'formik'; +import PropTypes from 'prop-types'; +import { useIntl } from 'react-intl'; + +import { getTrad } from '../../../utils'; +import schema from '../utils/schema'; + +const EmailForm = ({ template, onToggle, onSubmit }) => { + const { formatMessage } = useIntl(); + + return ( + + + + + {formatMessage({ + id: getTrad('PopUpForm.header.edit.email-templates'), + defaultMessage: 'Edit email template', + })} + + + {formatMessage({ id: getTrad(template.display), defaultMessage: template.display })} + + + + + {({ errors, values, handleChange, isSubmitting }) => { + return ( +
    + + + + + + + + + + + + + + + +