├── .github ├── dependabot.yml └── workflows │ ├── codeql.yml │ ├── seeder.yml │ └── tracker.yml ├── CONTRIBUTING.md ├── CREDITS.md ├── FAQ.md ├── LICENSE ├── README.md ├── backend ├── .env.example ├── .gitattributes ├── .gitignore ├── README.md ├── django_core │ ├── apps │ │ ├── anime │ │ │ ├── __init__.py │ │ │ ├── admin │ │ │ │ ├── __init__.py │ │ │ │ ├── anime_genre.py │ │ │ │ ├── anime_openings_and_endings.py │ │ │ │ └── anime_theme.py │ │ │ ├── apps.py │ │ │ ├── forms.py │ │ │ ├── migrations │ │ │ │ ├── 0001_initial.py │ │ │ │ ├── 0002_alter_animemodel_rating.py │ │ │ │ ├── 0002_animemodel_staffs_alter_animemodel_rating.py │ │ │ │ ├── 0003_remove_animemodel_theme_endings_and_more.py │ │ │ │ ├── 0004_alter_animeendingmodel_options_and_more.py │ │ │ │ ├── 0005_alter_animeendingmodel_unique_together_and_more.py │ │ │ │ ├── 0006_merge_20230326_1819.py │ │ │ │ ├── 0007_animemodel_created_at_alter_animemodel_updated.py │ │ │ │ ├── 0008_rename_updated_animemodel_updated_at.py │ │ │ │ ├── 0009_animegenremodel_description.py │ │ │ │ ├── 0010_animemodel_is_locked.py │ │ │ │ ├── 0011_animegenremodel_created_at_animegenremodel_is_locked_and_more.py │ │ │ │ ├── 0012_animethememodel_created_at_animethememodel_is_locked_and_more.py │ │ │ │ ├── 0013_animethememodel_description.py │ │ │ │ ├── 0014_alter_animegenremodel_is_locked_and_more.py │ │ │ │ ├── 0015_animemodel_anime_anime_name_301f1e_gin_and_more.py │ │ │ │ ├── 0016_remove_animemodel_anime_anime_name_301f1e_gin_and_more.py │ │ │ │ ├── 0017_remove_animemodel_anime_name_name_japanese_idx_and_more.py │ │ │ │ ├── 0018_alter_animenamesynonymmodel_name_and_more.py │ │ │ │ ├── 0019_animemodel_anime_name_name_japanese_idx.py │ │ │ │ ├── 0020_alter_animegenremodel_type.py │ │ │ │ ├── 0021_alter_animemodel_banner_background_color_and_more.py │ │ │ │ ├── 0022_alter_animegenremodel_created_at_and_more.py │ │ │ │ ├── 0023_alter_animegenremodel_is_locked_and_more.py │ │ │ │ ├── 0024_animecommentmodel_animemodel_comments_and_more.py │ │ │ │ ├── 0025_alter_animemodel_comments_delete_animecommentmodel.py │ │ │ │ ├── 0026_alter_animegenremodel_is_locked_and_more.py │ │ │ │ └── __init__.py │ │ │ ├── models │ │ │ │ ├── __init__.py │ │ │ │ ├── anime_genre.py │ │ │ │ ├── anime_openings_and_endings.py │ │ │ │ └── anime_theme.py │ │ │ └── signals.py │ │ ├── api │ │ │ ├── __init__.py │ │ │ ├── admin.py │ │ │ ├── auth.py │ │ │ ├── decorator.py │ │ │ ├── filters │ │ │ │ ├── anime.py │ │ │ │ ├── characters.py │ │ │ │ ├── genres.py │ │ │ │ ├── openings_and_endings.py │ │ │ │ ├── producers.py │ │ │ │ ├── staffs.py │ │ │ │ ├── studios.py │ │ │ │ └── themes.py │ │ │ ├── http.py │ │ │ ├── migrations │ │ │ │ └── 0001_initial.py │ │ │ ├── models.py │ │ │ ├── parser.py │ │ │ ├── permissions.py │ │ │ ├── schemas │ │ │ │ ├── anime │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── anime_genre.py │ │ │ │ │ ├── anime_opening_and_ending.py │ │ │ │ │ └── anime_theme.py │ │ │ │ ├── characters │ │ │ │ │ └── __init__.py │ │ │ │ ├── episodes │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── episode_timestamp.py │ │ │ │ ├── producers │ │ │ │ │ └── __init__.py │ │ │ │ ├── staffs │ │ │ │ │ └── __init__.py │ │ │ │ ├── stats │ │ │ │ │ └── histogram.py │ │ │ │ ├── trackers │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── anilist.py │ │ │ │ │ ├── kitsu.py │ │ │ │ │ └── mal.py │ │ │ │ └── user │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── login.py │ │ │ │ │ └── username_validity.py │ │ │ ├── urls.py │ │ │ └── views │ │ │ │ ├── anime │ │ │ │ ├── __init__.py │ │ │ │ ├── anime_character.py │ │ │ │ ├── anime_endings.py │ │ │ │ ├── anime_genre.py │ │ │ │ ├── anime_openings.py │ │ │ │ ├── anime_producer.py │ │ │ │ ├── anime_staff.py │ │ │ │ ├── anime_studio.py │ │ │ │ ├── anime_theme.py │ │ │ │ ├── endings.py │ │ │ │ ├── episode.py │ │ │ │ ├── genres.py │ │ │ │ ├── openings.py │ │ │ │ └── themes.py │ │ │ │ ├── characters │ │ │ │ └── __init__.py │ │ │ │ ├── producers │ │ │ │ └── __init__.py │ │ │ │ ├── staffs │ │ │ │ └── __init__.py │ │ │ │ └── user │ │ │ │ ├── __init__.py │ │ │ │ ├── login.py │ │ │ │ ├── logout.py │ │ │ │ ├── signup.py │ │ │ │ └── username_validity.py │ │ ├── characters │ │ │ ├── __init__.py │ │ │ ├── admin.py │ │ │ ├── apps.py │ │ │ ├── migrations │ │ │ │ ├── 0001_initial.py │ │ │ │ ├── 0002_charactermodel_created_at_charactermodel_updated_at.py │ │ │ │ ├── 0003_charactermodel_is_locked.py │ │ │ │ ├── 0004_alter_charactermodel_is_locked.py │ │ │ │ ├── 0005_alter_charactermodel_created_at.py │ │ │ │ ├── 0006_alter_charactermodel_is_locked.py │ │ │ │ ├── 0007_alter_charactermodel_is_locked.py │ │ │ │ └── __init__.py │ │ │ └── models.py │ │ ├── comments │ │ │ ├── __init__.py │ │ │ ├── admin.py │ │ │ ├── apps.py │ │ │ ├── migrations │ │ │ │ ├── 0001_initial.py │ │ │ │ ├── 0002_alter_commentmodel_options.py │ │ │ │ ├── 0003_commentmodel_dislikes_commentmodel_likes.py │ │ │ │ ├── 0004_alter_commentmodel_dislikes_alter_commentmodel_likes.py │ │ │ │ ├── 0005_remove_commentmodel_dislikes_and_more.py │ │ │ │ ├── 0006_commentmodel_deleted.py │ │ │ │ ├── 0007_alter_commentmodel_user.py │ │ │ │ └── __init__.py │ │ │ ├── models.py │ │ │ ├── tests.py │ │ │ └── views.py │ │ ├── episodes │ │ │ ├── __init__.py │ │ │ ├── admin.py │ │ │ ├── admin │ │ │ │ ├── __init__.py │ │ │ │ └── episode_timestamp.py │ │ │ ├── apps.py │ │ │ ├── forms.py │ │ │ ├── migrations │ │ │ │ ├── 0001_initial.py │ │ │ │ ├── 0002_episodemodel_episode_type.py │ │ │ │ ├── 0003_alter_episodecommentmodel_options_and_more.py │ │ │ │ ├── 0004_episodemodel_created_at_episodemodel_updated_at.py │ │ │ │ ├── 0005_remove_episodecommentmodel_comment_added_and_more.py │ │ │ │ ├── 0006_alter_episodecommentmodel_options_and_more.py │ │ │ │ ├── 0007_remove_episodetimestampmodel_episode.py │ │ │ │ ├── 0008_alter_episodecommentmodel_options.py │ │ │ │ ├── 0009_alter_episodecommentmodel_created_at_and_more.py │ │ │ │ ├── 0010_alter_episodemodel_episode_comments_and_more.py │ │ │ │ └── __init__.py │ │ │ └── models │ │ │ │ ├── __init__.py │ │ │ │ └── episode_timestamp.py │ │ ├── producers │ │ │ ├── __init__.py │ │ │ ├── admin.py │ │ │ ├── apps.py │ │ │ ├── migrations │ │ │ │ ├── 0001_initial.py │ │ │ │ ├── 0002_producermodel_created_at_producermodel_updated_at.py │ │ │ │ ├── 0003_remove_producermodel_default_title.py │ │ │ │ ├── 0004_rename_japanese_title_producermodel_name_japanese.py │ │ │ │ ├── 0005_producermodel_is_locked.py │ │ │ │ ├── 0006_alter_producermodel_is_locked.py │ │ │ │ ├── 0007_alter_producermodel_created_at.py │ │ │ │ ├── 0008_alter_producermodel_is_locked.py │ │ │ │ ├── 0009_alter_producermodel_is_locked.py │ │ │ │ └── __init__.py │ │ │ └── models.py │ │ ├── staffs │ │ │ ├── __init__.py │ │ │ ├── admin.py │ │ │ ├── apps.py │ │ │ ├── migrations │ │ │ │ ├── 0001_initial.py │ │ │ │ ├── 0002_alter_staffalternatenamemodel_options.py │ │ │ │ ├── 0003_staffmodel_created_at_staffmodel_updated_at.py │ │ │ │ ├── 0004_staffmodel_is_locked.py │ │ │ │ ├── 0005_alter_staffmodel_is_locked.py │ │ │ │ ├── 0006_staffalternatenamemodel_staff_alternate_idx_and_more.py │ │ │ │ ├── 0007_alter_staffmodel_created_at.py │ │ │ │ ├── 0008_alter_staffmodel_is_locked.py │ │ │ │ ├── 0009_alter_staffmodel_is_locked.py │ │ │ │ └── __init__.py │ │ │ └── models.py │ │ └── user │ │ │ ├── __init__.py │ │ │ ├── admin.py │ │ │ ├── apps.py │ │ │ ├── backends.py │ │ │ ├── forms.py │ │ │ ├── managers.py │ │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_alter_token_unique_together.py │ │ │ ├── 0003_delete_token.py │ │ │ ├── 0004_alter_customuser_avatar.py │ │ │ ├── 0005_remove_customuser_ip.py │ │ │ ├── 0006_customuser_created_at.py │ │ │ ├── 0007_alter_customuser_unique_together_and_more.py │ │ │ ├── 0008_alter_customuser_username.py │ │ │ ├── 0008_remove_customuser_created_at.py │ │ │ ├── 0009_merge_20240317_1131.py │ │ │ ├── 0010_customuser_created_at_alter_customuser_username.py │ │ │ ├── 0011_remove_customuser_created_at.py │ │ │ └── __init__.py │ │ │ ├── models.py │ │ │ ├── tests.py │ │ │ ├── urls.py │ │ │ ├── validators │ │ │ ├── __init__.py │ │ │ └── username.py │ │ │ └── views.py │ ├── core │ │ ├── __init__.py │ │ ├── asgi.py │ │ ├── celery.py │ │ ├── settings.py │ │ ├── storages.py │ │ ├── urls.py │ │ └── views.py │ ├── manage.py │ ├── media │ │ ├── anime │ │ │ └── .gitignore │ │ ├── avatars │ │ │ └── .gitignore │ │ ├── banner │ │ │ └── .gitignore │ │ ├── characters │ │ │ └── .gitignore │ │ ├── cover │ │ │ └── .gitignore │ │ ├── ending │ │ │ └── .gitignore │ │ ├── episode │ │ │ └── .gitignore │ │ ├── episode_cover │ │ │ └── .gitignore │ │ ├── opening │ │ │ └── .gitignore │ │ └── staffs │ │ │ └── .gitignore │ ├── mixins │ │ └── models │ │ │ ├── created_at.py │ │ │ ├── is_locked.py │ │ │ └── updated_at.py │ ├── static_src │ │ └── .gitkeep │ ├── templates │ │ ├── admin │ │ │ └── base.html │ │ ├── anime │ │ │ ├── _layout.html │ │ │ ├── episode │ │ │ │ └── index.html │ │ │ ├── explore │ │ │ │ └── index.html │ │ │ ├── index.html │ │ │ └── info │ │ │ │ └── index.html │ │ ├── errors │ │ │ └── base.html │ │ ├── home │ │ │ └── index.html │ │ ├── stack │ │ │ └── index.html │ │ ├── tailwind_base.html │ │ ├── upload │ │ │ └── index.html │ │ └── user │ │ │ ├── _layout.html │ │ │ ├── login │ │ │ └── index.html │ │ │ ├── register │ │ │ └── index.html │ │ │ ├── reset_password │ │ │ └── index.html │ │ │ └── user_does_not_exist.php │ ├── test │ │ └── api │ │ │ └── character │ │ │ └── test_response.py │ └── utilities │ │ ├── format.py │ │ └── rgb_to_hex.py ├── pyproject.toml └── uv.lock ├── discord ├── .gitignore ├── TODO.md ├── __init__.py ├── alembic.ini ├── alembic │ ├── README │ ├── env.py │ └── script.py.mako ├── database.py ├── models │ └── .gitkeep ├── poetry.lock ├── pyproject.toml ├── settings.py └── web │ ├── main.py │ ├── template │ └── .gitkeep │ └── views │ ├── .gitkeep │ └── login.py ├── docker-compose.yml ├── seeder ├── .gitattributes ├── .github │ └── dependabot.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── README.md ├── build.py ├── build_config.json ├── components.json ├── eslint.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── pyproject.toml ├── run.py ├── scripts │ └── replaceAssets.ts ├── src │ ├── app.css │ ├── app.d.ts │ ├── app.html │ ├── demo.spec.ts │ ├── global.d.ts │ ├── lib │ │ ├── components │ │ │ └── ui │ │ │ │ ├── button │ │ │ │ ├── button.svelte │ │ │ │ └── index.ts │ │ │ │ └── separator │ │ │ │ ├── index.ts │ │ │ │ └── separator.svelte │ │ ├── index.ts │ │ ├── interface │ │ │ └── pyloid.ts │ │ └── utils.ts │ └── routes │ │ ├── +layout.svelte │ │ ├── +page.svelte │ │ ├── server │ │ └── +page.svelte │ │ └── welcome │ │ ├── +layout.svelte │ │ └── +page.svelte ├── src_pyloid │ ├── __init__.py │ ├── bridge │ │ ├── __init__.py │ │ └── server.py │ ├── functions │ │ ├── __init__.py │ │ └── port.py │ ├── icons │ │ ├── icon.icns │ │ ├── icon.ico │ │ └── icon.png │ ├── main.py │ ├── managers │ │ └── __init__.py │ └── server │ │ ├── __init__.py │ │ └── websocket.py ├── static │ └── favicon.png ├── svelte.config.js ├── tailwind.config.ts ├── tsconfig.json ├── uv.lock └── vite.config.ts └── tracker ├── backend ├── .dockerignore ├── .gitattributes ├── .gitignore ├── .python-version ├── Dockerfile ├── README.md ├── coreproject_tracker │ ├── __init__.py │ ├── app.py │ ├── cli.py │ ├── constants │ │ ├── __init__.py │ │ ├── interval.py │ │ ├── peers.py │ │ ├── redis.py │ │ ├── ttl.py │ │ ├── udp.py │ │ └── websocket.py │ ├── converters │ │ ├── __init__.py │ │ ├── bytes.py │ │ ├── ip.py │ │ ├── numbers.py │ │ └── url.py │ ├── datastructures │ │ ├── __init__.py │ │ ├── immutable │ │ │ ├── __init__.py │ │ │ ├── http.py │ │ │ ├── redis.py │ │ │ ├── udp.py │ │ │ └── websocket.py │ │ └── mutable │ │ │ ├── __init__.py │ │ │ └── box.py │ ├── enums │ │ ├── __init__.py │ │ ├── actions.py │ │ ├── enum.py │ │ └── ip.py │ ├── envs │ │ ├── __init__.py │ │ ├── redis.py │ │ └── workers.py │ ├── functions │ │ ├── __init__.py │ │ ├── array.py │ │ ├── bytes.py │ │ ├── convertion.py │ │ ├── dictionary.py │ │ ├── events.py │ │ ├── ip.py │ │ └── redis.py │ ├── servers │ │ ├── __init__.py │ │ ├── http.py │ │ ├── udp.py │ │ └── websocket.py │ ├── transaction │ │ ├── __init__.py │ │ └── rollback.py │ └── validators │ │ ├── __init__.py │ │ ├── connection.py │ │ ├── ip.py │ │ ├── length.py │ │ ├── peer.py │ │ └── port.py ├── pyproject.toml ├── tests │ ├── test_webrtc.html │ ├── test_websocket.html │ ├── udp_client.py │ └── websocket_client.py └── uv.lock ├── deploy.sh ├── docker-compose.yml ├── frontend ├── .dockerignore ├── .gitignore ├── .npmrc ├── .prettierrc ├── Dockerfile ├── README.md ├── app.json ├── components.json ├── eslint.config.mjs ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public │ └── torrent-tester.html ├── src │ ├── app │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── health │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ └── page.tsx │ ├── components │ │ ├── navbar.tsx │ │ ├── theme-provider.tsx │ │ └── ui │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ └── dropdown-menu.tsx │ ├── constants │ │ └── url.ts │ ├── fonts │ │ └── Kokoro │ │ │ ├── Kokoro-Bold.woff2 │ │ │ ├── Kokoro-BoldItalic.woff2 │ │ │ ├── Kokoro-Italic.woff2 │ │ │ ├── Kokoro-Regular.woff2 │ │ │ ├── Kokoro-SemiBold.woff2 │ │ │ └── Kokoro-SemiBoldItalic.woff2 │ ├── functions │ │ └── bencode.ts │ ├── hooks │ │ ├── useBackendData.ts │ │ └── useHttpData.ts │ ├── icons │ │ ├── coreproject │ │ │ ├── CoreIcon.svg │ │ │ └── CoreProject.svg │ │ └── logos │ │ │ ├── python.svg │ │ │ ├── quart.svg │ │ │ └── redis.svg │ ├── lib │ │ └── utils.ts │ └── types │ │ ├── api.ts │ │ └── iframe.ts ├── svgr.d.ts └── tsconfig.json └── nginx ├── Dockerfile └── nginx.conf /.github/workflows/seeder.yml: -------------------------------------------------------------------------------- 1 | name: Seeder 2 | 3 | on: 4 | push: 5 | branches: ['master'] 6 | paths: ['seeder/**'] 7 | pull_request: 8 | branches: ['master'] 9 | paths: ['seeder/**'] 10 | 11 | defaults: 12 | run: 13 | working-directory: seeder 14 | 15 | jobs: 16 | build: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: 22 | - ubuntu-latest 23 | - macos-latest 24 | - windows-latest 25 | name: Build on ${{ matrix.os }} 26 | steps: 27 | - name: Checkout Seeder 28 | uses: actions/checkout@v4 29 | - name: Setup Node 30 | uses: actions/setup-node@v4 31 | with: 32 | cache: 'npm' 33 | cache-dependency-path: seeder/package-lock.json 34 | - name: Install Dependencies 35 | run: npm ci 36 | - name: Run Build 37 | run: npm run build 38 | -------------------------------------------------------------------------------- /.github/workflows/tracker.yml: -------------------------------------------------------------------------------- 1 | name: tracker 2 | 3 | on: 4 | push: 5 | branches: ['master'] 6 | paths: ['tracker/**'] 7 | pull_request: 8 | branches: ['master'] 9 | paths: ['tracker/**'] 10 | 11 | jobs: 12 | build-frontend: 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: 18 | - ubuntu-latest 19 | - macos-latest 20 | - windows-latest 21 | name: Build on ${{ matrix.os }} 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@v4 25 | 26 | - name: Setup Node.js 27 | uses: actions/setup-node@v4 28 | with: 29 | cache: 'npm' 30 | cache-dependency-path: tracker/frontend/package-lock.json 31 | 32 | - name: Install frontend dependencies 33 | run: npm ci 34 | working-directory: tracker/frontend 35 | 36 | - name: Build frontend 37 | run: npm run build 38 | working-directory: tracker/frontend 39 | -------------------------------------------------------------------------------- /CREDITS.md: -------------------------------------------------------------------------------- 1 | # Backend Testing 2 | 3 | [HoriDesu](https://github.com/Praveensenpai) 4 | -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | # POSTGRESQL SETTINGS 2 | POSTGRES_NAME = 3 | POSTGRES_USER = 4 | POSTGRES_PASSWORD = 5 | POSTGRES_HOST = 6 | POSTGRES_PORT = 7 | -------------------------------------------------------------------------------- /backend/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Django-Template 2 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/apps/anime/__init__.py -------------------------------------------------------------------------------- /backend/django_core/apps/anime/admin/anime_genre.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from ..models.anime_genre import AnimeGenreModel 4 | 5 | # Register your models here. 6 | 7 | 8 | @admin.register(AnimeGenreModel) 9 | class AnimeGenreAdmin(admin.ModelAdmin[AnimeGenreModel]): 10 | search_fields = ["name"] 11 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/admin/anime_openings_and_endings.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from ..models.anime_openings_and_endings import AnimeEndingModel, AnimeOpeningModel 4 | 5 | 6 | @admin.register(AnimeEndingModel) 7 | class AnimeEndingAdmin(admin.ModelAdmin[AnimeEndingModel]): 8 | search_fields = ["name"] 9 | 10 | 11 | @admin.register(AnimeOpeningModel) 12 | class AnimeOpeningAdmin(admin.ModelAdmin[AnimeOpeningModel]): 13 | search_fields = ["name"] 14 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/admin/anime_theme.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from ..models.anime_theme import AnimeThemeModel 4 | 5 | # Register your models here. 6 | 7 | 8 | @admin.register(AnimeThemeModel) 9 | class AnimeThemeAdmin(admin.ModelAdmin[AnimeThemeModel]): 10 | search_fields = ["name"] 11 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AnimeConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.anime" 7 | 8 | def ready(self) -> None: 9 | from . import signals as signals 10 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django_hstore_widget.forms import HStoreFormField 3 | 4 | from .models import AnimeModel 5 | 6 | 7 | class AnimeAdminModelForm(forms.ModelForm[AnimeModel]): 8 | theme_openings = HStoreFormField() 9 | theme_endings = HStoreFormField() 10 | 11 | class Meta: 12 | model = AnimeModel 13 | fields = "__all__" 14 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0002_alter_animemodel_rating.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-10 10:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0001_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="animemodel", 14 | name="rating", 15 | field=models.CharField(blank=True, default="", max_length=50), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0002_animemodel_staffs_alter_animemodel_rating.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-10 10:47 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("staffs", "0001_initial"), 9 | ("anime", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="animemodel", 15 | name="staffs", 16 | field=models.ManyToManyField(blank=True, to="staffs.staffmodel"), 17 | ), 18 | migrations.AlterField( 19 | model_name="animemodel", 20 | name="rating", 21 | field=models.CharField(blank=True, default="", max_length=50), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0004_alter_animeendingmodel_options_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-22 15:45 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0003_remove_animemodel_theme_endings_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="animeendingmodel", 14 | options={ 15 | "verbose_name": "Anime Ending", 16 | "verbose_name_plural": "Anime Endings", 17 | }, 18 | ), 19 | migrations.AlterModelOptions( 20 | name="animeopeningmodel", 21 | options={ 22 | "verbose_name": "Anime Opening", 23 | "verbose_name_plural": "Anime Openings", 24 | }, 25 | ), 26 | migrations.AddField( 27 | model_name="animeendingmodel", 28 | name="url", 29 | field=models.URLField(null=True), 30 | ), 31 | migrations.AddField( 32 | model_name="animeopeningmodel", 33 | name="url", 34 | field=models.URLField(null=True), 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0005_alter_animeendingmodel_unique_together_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-22 15:47 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0004_alter_animeendingmodel_options_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterUniqueTogether( 13 | name="animeendingmodel", 14 | unique_together={("entry", "name", "url")}, 15 | ), 16 | migrations.AlterUniqueTogether( 17 | name="animeopeningmodel", 18 | unique_together={("entry", "name", "url")}, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0006_merge_20230326_1819.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-26 18:19 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0002_alter_animemodel_rating"), 9 | ("anime", "0005_alter_animeendingmodel_unique_together_and_more"), 10 | ] 11 | 12 | operations = [] 13 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0007_animemodel_created_at_alter_animemodel_updated.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:13 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0006_merge_20230326_1819"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="animemodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | migrations.AlterField( 22 | model_name="animemodel", 23 | name="updated", 24 | field=models.DateTimeField(auto_now=True), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0008_rename_updated_animemodel_updated_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:14 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0007_animemodel_created_at_alter_animemodel_updated"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RenameField( 13 | model_name="animemodel", 14 | old_name="updated", 15 | new_name="updated_at", 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0009_animegenremodel_description.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-19 13:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0008_rename_updated_animemodel_updated_at"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="animegenremodel", 14 | name="description", 15 | field=models.TextField(null=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0010_animemodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-24 07:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0009_animegenremodel_description"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="animemodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | preserve_default=False, 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0011_animegenremodel_created_at_animegenremodel_is_locked_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-24 14:44 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0010_animemodel_is_locked"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="animegenremodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | migrations.AddField( 22 | model_name="animegenremodel", 23 | name="is_locked", 24 | field=models.BooleanField(default=False), 25 | preserve_default=False, 26 | ), 27 | migrations.AddField( 28 | model_name="animegenremodel", 29 | name="updated_at", 30 | field=models.DateTimeField(auto_now=True), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0012_animethememodel_created_at_animethememodel_is_locked_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-24 15:06 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0011_animegenremodel_created_at_animegenremodel_is_locked_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="animethememodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | migrations.AddField( 22 | model_name="animethememodel", 23 | name="is_locked", 24 | field=models.BooleanField(default=False), 25 | preserve_default=False, 26 | ), 27 | migrations.AddField( 28 | model_name="animethememodel", 29 | name="updated_at", 30 | field=models.DateTimeField(auto_now=True), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0013_animethememodel_description.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-24 15:43 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0012_animethememodel_created_at_animethememodel_is_locked_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="animethememodel", 14 | name="description", 15 | field=models.TextField(null=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0014_alter_animegenremodel_is_locked_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-25 19:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0013_animethememodel_description"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="animegenremodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | ), 17 | migrations.AlterField( 18 | model_name="animemodel", 19 | name="is_locked", 20 | field=models.BooleanField(default=False), 21 | ), 22 | migrations.AlterField( 23 | model_name="animethememodel", 24 | name="is_locked", 25 | field=models.BooleanField(default=False), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0015_animemodel_anime_anime_name_301f1e_gin_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.2 on 2023-06-17 14:01 2 | 3 | import django.contrib.postgres.indexes 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0014_alter_animegenremodel_is_locked_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddIndex( 14 | model_name="animemodel", 15 | index=django.contrib.postgres.indexes.GinIndex( 16 | fields=["name", "name_japanese"], name="anime_anime_name_301f1e_gin" 17 | ), 18 | ), 19 | migrations.AddIndex( 20 | model_name="animenamesynonymmodel", 21 | index=django.contrib.postgres.indexes.GinIndex( 22 | fields=["name"], name="anime_anime_name_81fa03_gin" 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0016_remove_animemodel_anime_anime_name_301f1e_gin_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.2 on 2023-06-17 14:08 2 | 3 | import django.contrib.postgres.indexes 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0015_animemodel_anime_anime_name_301f1e_gin_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveIndex( 14 | model_name="animemodel", 15 | name="anime_anime_name_301f1e_gin", 16 | ), 17 | migrations.RemoveIndex( 18 | model_name="animenamesynonymmodel", 19 | name="anime_anime_name_81fa03_gin", 20 | ), 21 | migrations.AddIndex( 22 | model_name="animemodel", 23 | index=django.contrib.postgres.indexes.GinIndex( 24 | fields=["name", "name_japanese"], 25 | name="anime_name|name_japanese_idx", 26 | opclasses=["gin_trgm_ops", "gin_trgm_ops"], 27 | ), 28 | ), 29 | migrations.AddIndex( 30 | model_name="animenamesynonymmodel", 31 | index=django.contrib.postgres.indexes.GinIndex( 32 | fields=["name"], name="anime_name_synonym_idx", opclasses=["gin_trgm_ops"] 33 | ), 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0017_remove_animemodel_anime_name_name_japanese_idx_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.4 on 2023-08-29 04:11 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0016_remove_animemodel_anime_anime_name_301f1e_gin_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveIndex( 13 | model_name="animemodel", 14 | name="anime_name|name_japanese_idx", 15 | ), 16 | migrations.RemoveIndex( 17 | model_name="animenamesynonymmodel", 18 | name="anime_name_synonym_idx", 19 | ), 20 | migrations.AlterField( 21 | model_name="animemodel", 22 | name="name", 23 | field=models.CharField(db_index=True, max_length=1024, unique=True), 24 | ), 25 | migrations.AlterField( 26 | model_name="animemodel", 27 | name="name_japanese", 28 | field=models.CharField(blank=True, db_index=True, default="", max_length=1024), 29 | ), 30 | migrations.AlterField( 31 | model_name="animenamesynonymmodel", 32 | name="name", 33 | field=models.CharField(db_index=True, max_length=100, unique=True), 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0018_alter_animenamesynonymmodel_name_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.4 on 2023-08-29 04:12 2 | 3 | import django.contrib.postgres.indexes 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0017_remove_animemodel_anime_name_name_japanese_idx_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="animenamesynonymmodel", 15 | name="name", 16 | field=models.CharField(max_length=100, unique=True), 17 | ), 18 | migrations.AddIndex( 19 | model_name="animenamesynonymmodel", 20 | index=django.contrib.postgres.indexes.GinIndex( 21 | fields=["name"], name="anime_name_synonym_idx", opclasses=["gin_trgm_ops"] 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0019_animemodel_anime_name_name_japanese_idx.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.4 on 2023-08-29 04:12 2 | 3 | import django.contrib.postgres.indexes 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0018_alter_animenamesynonymmodel_name_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddIndex( 14 | model_name="animemodel", 15 | index=django.contrib.postgres.indexes.GinIndex( 16 | fields=["name", "name_japanese"], 17 | name="anime_name|name_japanese_idx", 18 | opclasses=["gin_trgm_ops", "gin_trgm_ops"], 19 | ), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0020_alter_animegenremodel_type.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.4 on 2023-08-29 06:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0019_animemodel_anime_name_name_japanese_idx"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="animegenremodel", 14 | name="type", 15 | field=models.CharField( 16 | choices=[("Anime", "anime"), ("Manga", "manga")], default="", max_length=50 17 | ), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0021_alter_animemodel_banner_background_color_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.5 on 2023-09-09 04:19 2 | 3 | import colorfield.fields 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0020_alter_animegenremodel_type"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="animemodel", 15 | name="banner_background_color", 16 | field=colorfield.fields.ColorField( 17 | blank=True, 18 | default=None, 19 | image_field=None, 20 | max_length=25, 21 | null=True, 22 | samples=None, 23 | ), 24 | ), 25 | migrations.AlterField( 26 | model_name="animemodel", 27 | name="cover_background_color", 28 | field=colorfield.fields.ColorField( 29 | blank=True, 30 | default=None, 31 | image_field=None, 32 | max_length=25, 33 | null=True, 34 | samples=None, 35 | ), 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0022_alter_animegenremodel_created_at_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 05:51 2 | 3 | import django.db.models.functions.datetime 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("anime", "0021_alter_animemodel_banner_background_color_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="animegenremodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | db_default=django.db.models.functions.datetime.Now() 18 | ), 19 | ), 20 | migrations.AlterField( 21 | model_name="animemodel", 22 | name="created_at", 23 | field=models.DateTimeField( 24 | db_default=django.db.models.functions.datetime.Now() 25 | ), 26 | ), 27 | migrations.AlterField( 28 | model_name="animethememodel", 29 | name="created_at", 30 | field=models.DateTimeField( 31 | db_default=django.db.models.functions.datetime.Now() 32 | ), 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0023_alter_animegenremodel_is_locked_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 09:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0022_alter_animegenremodel_created_at_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="animegenremodel", 14 | name="is_locked", 15 | field=models.BooleanField(db_default=models.Value(False)), 16 | ), 17 | migrations.AlterField( 18 | model_name="animemodel", 19 | name="is_locked", 20 | field=models.BooleanField(db_default=models.Value(False)), 21 | ), 22 | migrations.AlterField( 23 | model_name="animethememodel", 24 | name="is_locked", 25 | field=models.BooleanField(db_default=models.Value(False)), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0025_alter_animemodel_comments_delete_animecommentmodel.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0rc1 on 2023-12-05 13:05 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("anime", "0024_animecommentmodel_animemodel_comments_and_more"), 9 | ("comments", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="animemodel", 15 | name="comments", 16 | field=models.ManyToManyField(blank=True, to="comments.commentmodel"), 17 | ), 18 | migrations.DeleteModel( 19 | name="AnimeCommentModel", 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/0026_alter_animegenremodel_is_locked_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-21 03:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("anime", "0025_alter_animemodel_comments_delete_animecommentmodel"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="animegenremodel", 15 | name="is_locked", 16 | field=models.BooleanField(db_default=False), 17 | ), 18 | migrations.AlterField( 19 | model_name="animemodel", 20 | name="is_locked", 21 | field=models.BooleanField(db_default=False), 22 | ), 23 | migrations.AlterField( 24 | model_name="animethememodel", 25 | name="is_locked", 26 | field=models.BooleanField(db_default=False), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/apps/anime/migrations/__init__.py -------------------------------------------------------------------------------- /backend/django_core/apps/anime/models/anime_genre.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from mixins.models.created_at import CreatedAtMixin 3 | from mixins.models.is_locked import IsLockedMixin 4 | from mixins.models.updated_at import UpdatedAtMixin 5 | 6 | # Create your models here. 7 | 8 | TYPE_CHOICES = [ 9 | ("Anime", "anime"), 10 | ("Manga", "manga"), 11 | ] 12 | 13 | 14 | class AnimeGenreModel(UpdatedAtMixin, CreatedAtMixin, IsLockedMixin): 15 | mal_id = models.IntegerField(unique=True, blank=False, null=False) 16 | name = models.CharField(unique=True, max_length=50, default="", null=False, blank=False) 17 | type = models.CharField( 18 | max_length=50, choices=TYPE_CHOICES, default="", null=False, blank=False 19 | ) 20 | description = models.TextField(null=True, blank=False) 21 | 22 | def __str__(self) -> str: 23 | return f"{self.pk}. {self.name} ({self.type})" 24 | 25 | class Meta: 26 | verbose_name = "Anime Genre" 27 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/models/anime_theme.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from mixins.models.created_at import CreatedAtMixin 3 | from mixins.models.is_locked import IsLockedMixin 4 | from mixins.models.updated_at import UpdatedAtMixin 5 | 6 | # Create your models here. 7 | 8 | TYPE_CHOICES = [ 9 | ("Anime", "anime"), 10 | ("Manga", "manga"), 11 | ] 12 | 13 | 14 | class AnimeThemeModel(UpdatedAtMixin, CreatedAtMixin, IsLockedMixin): 15 | mal_id = models.IntegerField(unique=True, blank=False, null=False) 16 | name = models.CharField(unique=True, max_length=50, default="", null=False, blank=False) 17 | type = models.CharField( 18 | max_length=50, 19 | choices=TYPE_CHOICES, 20 | default="", 21 | null=False, 22 | blank=False, 23 | ) 24 | description = models.TextField(null=True, blank=False) 25 | 26 | def __str__(self) -> str: 27 | return f"{self.pk}. {self.name} ({self.type})" 28 | 29 | class Meta: 30 | verbose_name = "Anime Theme" 31 | -------------------------------------------------------------------------------- /backend/django_core/apps/anime/signals.py: -------------------------------------------------------------------------------- 1 | from typing import TypedDict, Unpack 2 | 3 | from apps.anime.tasks import set_field_color 4 | from django.db.models.signals import post_save 5 | from django.dispatch import receiver 6 | 7 | from .models import AnimeModel 8 | 9 | 10 | class DjangoInstance(TypedDict): 11 | instance: AnimeModel 12 | 13 | 14 | @receiver(post_save, sender=AnimeModel) 15 | def banner_background_color_handler( 16 | **kwargs: Unpack[DjangoInstance], 17 | ) -> None: 18 | instance = kwargs["instance"] 19 | 20 | # Set Background Banner Image Color 21 | if (hasattr(instance, "banner")) and not instance.banner_background_color: 22 | set_field_color.delay( 23 | instance.pk, 24 | "banner_background_color", 25 | "banner", 26 | ) 27 | 28 | # Set Background Cover Image Color 29 | if (hasattr(instance, "cover")) and not instance.cover_background_color: 30 | set_field_color.delay( 31 | instance.pk, 32 | "cover_background_color", 33 | "cover", 34 | ) 35 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/apps/api/__init__.py -------------------------------------------------------------------------------- /backend/django_core/apps/api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Token 4 | 5 | 6 | @admin.register(Token) 7 | class TokenAdmin(admin.ModelAdmin[Token]): 8 | autocomplete_fields = ["user"] 9 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/auth.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Any 3 | 4 | from apps.user.models import CustomUser 5 | from django.conf import settings 6 | from django.contrib.auth.models import AnonymousUser 7 | from django.http import HttpRequest 8 | from ninja.security import HttpBearer 9 | 10 | from .models import Token 11 | 12 | logger = logging.getLogger("django") 13 | 14 | 15 | class AuthBearer(HttpBearer): 16 | def authenticate( 17 | self, 18 | request: HttpRequest, 19 | token: str, 20 | ) -> CustomUser | AnonymousUser: 21 | try: 22 | token_data = Token.objects.get(token=token) 23 | return token_data.user 24 | 25 | except Token.DoesNotExist: 26 | return AnonymousUser 27 | 28 | 29 | class OptionalAuthBearer(AuthBearer): 30 | def __call__(self, request: HttpRequest) -> Any | None: 31 | auth_value = request.headers.get(self.header) 32 | if not auth_value: 33 | return AnonymousUser() # if there is no key, we return AnonymousUser object 34 | parts = auth_value.split(" ") 35 | 36 | if parts[0].lower() != self.openapi_scheme: 37 | if settings.DEBUG: 38 | logger.error(f"Unexpected auth - '{auth_value}'") 39 | return None 40 | token = " ".join(parts[1:]) 41 | return self.authenticate(request, token) 42 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/decorator.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Callable 2 | from functools import wraps 3 | from http import HTTPStatus 4 | from typing import TYPE_CHECKING, Any 5 | 6 | from apps.user.models import CustomUser 7 | from django.contrib.auth.models import AnonymousUser 8 | from apps.api.http import HttpRequest 9 | from ninja.errors import HttpError 10 | 11 | if TYPE_CHECKING: 12 | from .permissions import IsSuperUser 13 | 14 | 15 | def permission_required( 16 | permissions: list[Callable[[HttpRequest, CustomUser], "IsSuperUser"]], 17 | key: str | None = "auth", # To get request.auth 18 | ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: 19 | def decorator(func: Callable[..., Any]) -> Callable[..., Any]: 20 | @wraps(func) 21 | def wrapper(request: HttpRequest, *args: Any, **kwargs: Any) -> Any: 22 | user = getattr(request, key, AnonymousUser) 23 | 24 | permission_granted = any( 25 | [permission(request, user).has_permissions() for permission in permissions] 26 | ) 27 | if not permission_granted: 28 | raise HttpError( 29 | HTTPStatus.UNAUTHORIZED, 30 | "Superuser is required for this operation", 31 | ) 32 | return func(request, *args, **kwargs) 33 | 34 | return wrapper 35 | 36 | return decorator 37 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/anime.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class AnimeInfoFilters(Schema): 5 | mal_id: int | None = None 6 | kitsu_id: int | None = None 7 | anilist_id: int | None = None 8 | 9 | # Relation based on 10 | # * name 11 | # * name_japanese 12 | # * name_synonyms__name 13 | name: str | None = None 14 | 15 | # Map as closely to model fields as possible. 16 | # So we can do something like 17 | # genres__name__icontains 18 | genres: str | None = None 19 | themes: str | None = None 20 | studios: str | None = None 21 | producers: str | None = None 22 | characters: str | None = None 23 | staffs: str | None = None 24 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/characters.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class CharacterFilter(Schema): 5 | mal_id: int | None = None 6 | kitsu_id: int | None = None 7 | anilist_id: int | None = None 8 | 9 | # icontains based search 10 | # Allowed : 11 | # Sora Amamiya, Natsukawa Shiina, Momo Asakura 12 | # Sora Amamiya 13 | name: str | None = None 14 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/genres.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class GenreFilter(Schema): 5 | name: str | None = None 6 | mal_id: int | None = None 7 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/openings_and_endings.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class OpeningAndEndingFilter(Schema): 5 | # Opening number 6 | entry: int | None = None 7 | # Opening/closing theme name 8 | name: str | None = None 9 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/producers.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class ProducerFilter(Schema): 5 | mal_id: int | None = None 6 | 7 | # icontains based search 8 | # Allowed : 9 | # A1 Pictures, Studio Ghibli 10 | # A1 Pictures 11 | name: str | None = None 12 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/staffs.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class StaffFilter(Schema): 5 | mal_id: int | None = None 6 | kitsu_id: int | None = None 7 | anilist_id: int | None = None 8 | 9 | # icontains based search 10 | # Allowed : 11 | # Sora Amamiya, Natsukawa Shiina, Momo Asakura 12 | # Sora Amamiya 13 | name: str | None = None 14 | 15 | given_name: str | None = None 16 | family_name: str | None = None 17 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/studios.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class StudioFilter(Schema): 5 | mal_id: int | None = None 6 | 7 | # icontains based search 8 | # Allowed : 9 | # A1 Pictures, Studio Ghibli 10 | # A1 Pictures 11 | name: str | None = None 12 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/filters/themes.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class ThemeFilter(Schema): 5 | name: str | None = None 6 | mal_id: int | None = None 7 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/http.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpRequest as DjangoHttpRequest 2 | from apps.user.models import CustomUser 3 | from django.contrib.auth.models import AnonymousUser 4 | 5 | 6 | class HttpRequest(DjangoHttpRequest): 7 | auth: CustomUser | AnonymousUser 8 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/models.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from apps.user.models import CustomUser 4 | from django.db import models 5 | from django.utils.crypto import get_random_string 6 | from django.utils.translation import gettext_lazy as _ 7 | 8 | 9 | class Token(models.Model): 10 | token = models.CharField( 11 | default=partial( 12 | get_random_string, 13 | 16, 14 | ), 15 | unique=True, 16 | max_length=16, 17 | editable=False, 18 | ) 19 | user = models.OneToOneField(CustomUser, on_delete=models.CASCADE, related_name="user") 20 | 21 | def __str__(self) -> str: 22 | return f"User : {self.user.username} | Token : {self.token}" 23 | 24 | class Meta: 25 | indexes = [ 26 | models.Index(fields=["token"]), 27 | ] 28 | verbose_name = _("token") 29 | verbose_name_plural = _("tokens") 30 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/parser.py: -------------------------------------------------------------------------------- 1 | from apps.api.http import HttpRequest 2 | from ninja.parser import Parser 3 | from ninja.types import DictStrAny 4 | 5 | try: 6 | import orjson 7 | 8 | HAS_ORJSON = True 9 | except ImportError: 10 | HAS_ORJSON = False 11 | 12 | 13 | class CustomParser(Parser): 14 | def __init__(self) -> None: 15 | # Override method only if `orjson`` exists 16 | if HAS_ORJSON: 17 | self.parse_body = self._parse_body 18 | 19 | def _parse_body(self, request: HttpRequest) -> DictStrAny: 20 | return orjson.loads(request.body) 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/permissions.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from apps.api.http import HttpRequest 3 | 4 | SAFE_METHODS = ("GET", "HEAD", "OPTIONS") 5 | 6 | 7 | class IsSuperUser: 8 | def __init__(self, request: HttpRequest, user: User) -> None: 9 | self.request = request 10 | self.user = user 11 | 12 | def has_permissions(self) -> bool: 13 | if self.user.is_superuser and self.request.method not in SAFE_METHODS: 14 | return True 15 | return False 16 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/anime/anime_genre.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models.anime_genre import AnimeGenreModel 2 | from ninja import ModelSchema 3 | 4 | 5 | class AnimeGenreGETSchema(ModelSchema): 6 | class Config: 7 | model = AnimeGenreModel 8 | model_fields = "__all__" 9 | 10 | 11 | class AnimeGenrePOSTSchema(ModelSchema): 12 | class Config: 13 | model = AnimeGenreModel 14 | model_exclude = [ 15 | "id", 16 | "type", 17 | "created_at", 18 | "updated_at", 19 | "is_locked", 20 | ] 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/anime/anime_opening_and_ending.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models.anime_openings_and_endings import AnimeOpeningModel 2 | from ninja import ModelSchema 3 | 4 | 5 | class AnimeOpeningAndEndingGETSchema(ModelSchema): 6 | class Config: 7 | model = AnimeOpeningModel 8 | model_fields = "__all__" 9 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/anime/anime_theme.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models.anime_theme import AnimeThemeModel 2 | from ninja import ModelSchema 3 | 4 | 5 | class AnimeThemeGETSchema(ModelSchema): 6 | class Config: 7 | model = AnimeThemeModel 8 | model_fields = "__all__" 9 | 10 | 11 | class AnimeThemePOSTSchema(ModelSchema): 12 | class Config: 13 | model = AnimeThemeModel 14 | model_exclude = [ 15 | "id", 16 | "type", 17 | "created_at", 18 | "updated_at", 19 | "is_locked", 20 | ] 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/characters/__init__.py: -------------------------------------------------------------------------------- 1 | from ninja import ModelSchema 2 | 3 | from ....characters.models import CharacterModel 4 | 5 | 6 | class CharacterGETSchema(ModelSchema): 7 | class Config: 8 | model = CharacterModel 9 | model_fields = "__all__" 10 | 11 | 12 | class CharacterPOSTSchema(ModelSchema): 13 | class Config: 14 | model = CharacterModel 15 | model_exclude = [ 16 | "id", 17 | "created_at", 18 | "updated_at", 19 | "is_locked", 20 | ] 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/episodes/__init__.py: -------------------------------------------------------------------------------- 1 | from apps.episodes.models import EpisodeModel 2 | from ninja import ModelSchema 3 | 4 | 5 | class EpisodeGETSchema(ModelSchema): 6 | class Config: 7 | model = EpisodeModel 8 | model_fields = "__all__" 9 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/episodes/episode_timestamp.py: -------------------------------------------------------------------------------- 1 | from apps.episodes.models.episode_timestamp import EpisodeTimestampModel 2 | from ninja import Field, ModelSchema, Schema 3 | 4 | 5 | class EpisodeTimestampTotalTimestampSchema(Schema): 6 | total_watchtime: int 7 | 8 | 9 | class EpisodeTimestampGETSchema(ModelSchema): 10 | user: str | None = Field(..., alias="user.username") 11 | 12 | class Config: 13 | model = EpisodeTimestampModel 14 | model_exclude = ["episode", "id"] 15 | 16 | 17 | class EpisodeTimestampPOSTSchema(ModelSchema): 18 | class Config: 19 | model = EpisodeTimestampModel 20 | model_exclude = ["episode", "id", "user", "created_at"] 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/producers/__init__.py: -------------------------------------------------------------------------------- 1 | from ninja import ModelSchema 2 | 3 | from ....producers.models import ProducerModel 4 | 5 | 6 | class ProducerGETSchema(ModelSchema): 7 | class Config: 8 | model = ProducerModel 9 | model_fields = "__all__" 10 | 11 | 12 | class ProducerPOSTSchema(ModelSchema): 13 | kitsu_id: int | None = None 14 | 15 | class Config: 16 | model = ProducerModel 17 | model_exclude = [ 18 | "id", 19 | "created_at", 20 | "updated_at", 21 | "is_locked", 22 | ] 23 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/staffs/__init__.py: -------------------------------------------------------------------------------- 1 | from ninja import ModelSchema 2 | 3 | from ....staffs.models import StaffAlternateNameModel, StaffModel 4 | 5 | 6 | class StaffAlternateNameSchema(ModelSchema): 7 | class Config: 8 | model = StaffAlternateNameModel 9 | model_fields = ["name"] 10 | 11 | 12 | class StaffGETSchema(ModelSchema): 13 | alternate_names: list[StaffAlternateNameSchema] = [] 14 | 15 | class Config: 16 | model = StaffModel 17 | model_fields = "__all__" 18 | 19 | 20 | class StaffPOSTSchema(ModelSchema): 21 | alternate_names: list[StaffAlternateNameSchema] = [] 22 | 23 | class Config: 24 | model = StaffModel 25 | model_exclude = [ 26 | "id", 27 | "created_at", 28 | "updated_at", 29 | "is_locked", 30 | ] 31 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/stats/histogram.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from ninja.schema import Schema 4 | 5 | 6 | class HistogramSchema(Schema): 7 | year: datetime.datetime 8 | month: datetime.datetime 9 | count: int 10 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/trackers/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from ninja import Schema 4 | 5 | 6 | class TrackerSchema(Schema): 7 | status: Literal[ 8 | "watching", 9 | "completed", 10 | "on_hold", 11 | "dropped", 12 | "plan_to_watch", 13 | ] 14 | is_rewatching: bool 15 | score: int 16 | num_watched_episodes: int 17 | priority: int 18 | num_times_rewatched: int 19 | rewatch_value: int 20 | tags: str 21 | comments: str 22 | 23 | 24 | class TrackerDeleteSchema(Schema): 25 | anime_id: int 26 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/trackers/anilist.py: -------------------------------------------------------------------------------- 1 | from apps.trackers.models import AnilistModel 2 | from ninja import ModelSchema 3 | 4 | 5 | class AnilistGETSchema(ModelSchema): 6 | class Config: 7 | model = AnilistModel 8 | model_fields = "__all__" 9 | 10 | 11 | class AnilistPOSTSchema(ModelSchema): 12 | class Config: 13 | model = AnilistModel 14 | model_exclude = ["user", "id", "created_at"] 15 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/trackers/kitsu.py: -------------------------------------------------------------------------------- 1 | from apps.trackers.models import KitsuModel 2 | from ninja import ModelSchema 3 | 4 | 5 | class KitsuGETSchema(ModelSchema): 6 | class Config: 7 | model = KitsuModel 8 | model_fields = "__all__" 9 | 10 | 11 | class KitsuPOSTSchema(ModelSchema): 12 | class Config: 13 | model = KitsuModel 14 | model_exclude = ["user", "id", "created_at"] 15 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/trackers/mal.py: -------------------------------------------------------------------------------- 1 | from apps.trackers.models import MalModel 2 | from ninja import ModelSchema 3 | 4 | 5 | class MALGETSchema(ModelSchema): 6 | class Config: 7 | model = MalModel 8 | model_fields = "__all__" 9 | 10 | 11 | class MALPOSTSchema(ModelSchema): 12 | class Config: 13 | model = MalModel 14 | model_exclude = ["user", "id", "created_at"] 15 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/user/__init__.py: -------------------------------------------------------------------------------- 1 | from apps.user.models import CustomUser 2 | from django.contrib.auth import get_user_model 3 | from django.urls import reverse_lazy 4 | from ninja import ModelSchema 5 | 6 | 7 | class UserSchema(ModelSchema): 8 | avatar: str 9 | 10 | class Config: 11 | model = get_user_model() 12 | model_exclude = [ 13 | "password", 14 | "avatar", 15 | ] 16 | 17 | @staticmethod 18 | def resolve_avatar(obj: CustomUser) -> str: 19 | url = reverse_lazy( 20 | "avatar_view", 21 | kwargs={ 22 | "user_id": obj.id, 23 | }, 24 | ) 25 | return f"{url}" 26 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/user/login.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class LoginSchema(Schema): 5 | token: str 6 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/schemas/user/username_validity.py: -------------------------------------------------------------------------------- 1 | from ninja import Schema 2 | 3 | 4 | class UsernameValiditySchema(Schema): 5 | status: int 6 | message: str 7 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/anime_endings.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models import AnimeModel 2 | from apps.anime.models.anime_openings_and_endings import AnimeOpeningModel 3 | from apps.api.http import HttpRequest 4 | from django.shortcuts import get_list_or_404, get_object_or_404 5 | from ninja import Router 6 | 7 | from ...schemas.anime.anime_opening_and_ending import AnimeOpeningAndEndingGETSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.get("/{int:anime_id}/endings", response=list[AnimeOpeningAndEndingGETSchema]) 13 | def get_individual_anime_endings_info( 14 | request: HttpRequest, 15 | anime_id: int, 16 | ) -> list[AnimeOpeningModel]: 17 | query = get_list_or_404( 18 | get_object_or_404(AnimeModel, pk=anime_id).endings, 19 | ) 20 | return query 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/anime_genre.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models import AnimeModel 2 | from apps.anime.models.anime_genre import AnimeGenreModel 3 | from apps.api.http import HttpRequest 4 | from django.shortcuts import get_list_or_404, get_object_or_404 5 | from ninja import Router 6 | 7 | from ...schemas.anime.anime_genre import AnimeGenreGETSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.get("/{int:anime_id}/genres", response=list[AnimeGenreGETSchema]) 13 | def get_individual_anime_genre_info( 14 | request: HttpRequest, 15 | anime_id: int, 16 | ) -> list[AnimeGenreModel]: 17 | query = get_list_or_404( 18 | get_object_or_404(AnimeModel, pk=anime_id).genres, 19 | ) 20 | return query 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/anime_openings.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models import AnimeModel 2 | from apps.anime.models.anime_openings_and_endings import AnimeOpeningModel 3 | from apps.api.http import HttpRequest 4 | from django.shortcuts import get_list_or_404, get_object_or_404 5 | from ninja import Router 6 | 7 | from ...schemas.anime.anime_opening_and_ending import AnimeOpeningAndEndingGETSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.get("/{int:anime_id}/openings", response=list[AnimeOpeningAndEndingGETSchema]) 13 | def get_individual_anime_opening_info( 14 | request: HttpRequest, 15 | anime_id: int, 16 | ) -> list[AnimeOpeningModel]: 17 | query = get_list_or_404( 18 | get_object_or_404(AnimeModel, pk=anime_id).openings, 19 | ) 20 | return query 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/anime_producer.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models import AnimeModel 2 | from apps.producers.models import ProducerModel 3 | from apps.api.http import HttpRequest 4 | from django.shortcuts import get_list_or_404, get_object_or_404 5 | from ninja import Router 6 | 7 | from ...schemas.producers import ProducerGETSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.get("/{int:anime_id}/producers", response=list[ProducerGETSchema]) 13 | def get_individual_anime_producer_info( 14 | request: HttpRequest, 15 | anime_id: int, 16 | ) -> list[ProducerModel]: 17 | query = get_list_or_404( 18 | get_object_or_404(AnimeModel, pk=anime_id).producers, 19 | ) 20 | return query 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/anime_staff.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models import AnimeModel 2 | from apps.producers.models import ProducerModel 3 | from apps.api.http import HttpRequest 4 | from django.shortcuts import get_list_or_404, get_object_or_404 5 | from ninja import Router 6 | 7 | from ...schemas.staffs import StaffGETSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.get("/{int:anime_id}/staffs", response=list[StaffGETSchema]) 13 | def get_individual_anime_staff_info( 14 | request: HttpRequest, 15 | anime_id: int, 16 | ) -> list[ProducerModel]: 17 | query = get_list_or_404( 18 | get_object_or_404(AnimeModel, pk=anime_id).staffs, 19 | ) 20 | return query 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/anime_studio.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models import AnimeModel 2 | from apps.api.http import HttpRequest 3 | from django.shortcuts import get_list_or_404, get_object_or_404 4 | from ninja import Router 5 | 6 | from ....producers.models import ProducerModel 7 | from ...schemas.producers import ProducerGETSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.get("/{int:anime_id}/studios", response=list[ProducerGETSchema]) 13 | def get_individual_anime_studio_info( 14 | request: HttpRequest, 15 | anime_id: int, 16 | ) -> list[ProducerModel]: 17 | query = get_list_or_404( 18 | get_object_or_404(AnimeModel, pk=anime_id).studios, 19 | ) 20 | 21 | return query 22 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/anime_theme.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models import AnimeModel 2 | from apps.anime.models.anime_theme import AnimeThemeModel 3 | from apps.api.http import HttpRequest 4 | from django.shortcuts import get_list_or_404, get_object_or_404 5 | from ninja import Router 6 | 7 | from ...schemas.anime.anime_theme import AnimeThemeGETSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.get("/{int:anime_id}/themes", response=list[AnimeThemeGETSchema]) 13 | def get_individual_anime_theme_info( 14 | request: HttpRequest, 15 | anime_id: int, 16 | ) -> list[AnimeThemeModel]: 17 | query = get_list_or_404( 18 | get_object_or_404(AnimeModel, pk=anime_id).themes, 19 | ) 20 | return query 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/endings.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models.anime_openings_and_endings import AnimeEndingModel 2 | from apps.api.auth import AuthBearer 3 | from apps.api.decorator import permission_required 4 | from apps.api.permissions import IsSuperUser 5 | from apps.api.http import HttpRequest 6 | from ninja import File, Form, Query, Router 7 | from ninja.files import UploadedFile 8 | from pydantic import AnyUrl 9 | 10 | from ...filters.openings_and_endings import OpeningAndEndingFilter 11 | from ...schemas.anime.anime_opening_and_ending import AnimeOpeningAndEndingGETSchema 12 | 13 | router = Router() 14 | 15 | 16 | @router.get("/endings", response=list[AnimeOpeningAndEndingGETSchema]) 17 | def get_anime_ending_info( 18 | request: HttpRequest, 19 | filters: OpeningAndEndingFilter = Query(...), 20 | ) -> list[AnimeEndingModel]: 21 | query = AnimeEndingModel.objects.filter(**filters.dict(exclude_none=True)) 22 | 23 | return query 24 | 25 | 26 | @router.post("/endings", response=AnimeOpeningAndEndingGETSchema, auth=AuthBearer()) 27 | @permission_required([IsSuperUser]) 28 | def post_anime_ending_info( 29 | request: HttpRequest, 30 | entry: int | None = Form(...), 31 | name: str | None = Form(...), 32 | url: AnyUrl = Form(...), 33 | thumbnail: UploadedFile | None = File(...), 34 | ) -> AnimeEndingModel: 35 | query = AnimeEndingModel.objects.create( 36 | entry=entry, 37 | name=name, 38 | url=url, 39 | thumbnail=thumbnail, 40 | ) 41 | return query 42 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/genres.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models.anime_genre import AnimeGenreModel 2 | from apps.api.auth import AuthBearer 3 | from apps.api.decorator import permission_required 4 | from apps.api.permissions import IsSuperUser 5 | from apps.api.http import HttpRequest 6 | from ninja import Query, Router 7 | 8 | from ...filters.genres import GenreFilter 9 | from ...schemas.anime.anime_genre import AnimeGenreGETSchema, AnimeGenrePOSTSchema 10 | 11 | router = Router() 12 | 13 | 14 | @router.get("/genres", response=list[AnimeGenreGETSchema]) 15 | def get_anime_genre_info( 16 | request: HttpRequest, 17 | filters: GenreFilter = Query(...), 18 | ) -> list[AnimeGenreModel]: 19 | query = AnimeGenreModel.objects.filter( 20 | type__icontains="anime", **filters.dict(exclude_none=True) 21 | ) 22 | 23 | return query 24 | 25 | 26 | @router.post("/genres", response=list[AnimeGenreGETSchema], auth=AuthBearer()) 27 | @permission_required([IsSuperUser]) 28 | def post_anime_genre_info( 29 | request: HttpRequest, 30 | payload: list[AnimeGenrePOSTSchema], 31 | ) -> list[AnimeGenreModel]: 32 | instance_objects = [] 33 | for object in payload: 34 | instance_objects.append( 35 | AnimeGenreModel( 36 | type="anime", 37 | **object.dict(exclude_none=True), 38 | ) 39 | ) 40 | 41 | query = AnimeGenreModel.objects.bulk_create(instance_objects) 42 | return query 43 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/openings.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models.anime_openings_and_endings import AnimeOpeningModel 2 | from apps.api.auth import AuthBearer 3 | from apps.api.decorator import permission_required 4 | from apps.api.permissions import IsSuperUser 5 | from apps.api.http import HttpRequest 6 | from ninja import File, Form, Query, Router 7 | from ninja.files import UploadedFile 8 | from pydantic import AnyUrl 9 | 10 | from ...filters.openings_and_endings import OpeningAndEndingFilter 11 | from ...schemas.anime.anime_opening_and_ending import AnimeOpeningAndEndingGETSchema 12 | 13 | router = Router() 14 | 15 | 16 | @router.get("/openings", response=list[AnimeOpeningAndEndingGETSchema]) 17 | def get_anime_opening_info( 18 | request: HttpRequest, 19 | filters: OpeningAndEndingFilter = Query(...), 20 | ) -> list[AnimeOpeningModel]: 21 | query = AnimeOpeningModel.objects.filter(**filters.dict(exclude_none=True)) 22 | 23 | return query 24 | 25 | 26 | @router.post("/openings", response=AnimeOpeningAndEndingGETSchema, auth=AuthBearer()) 27 | @permission_required([IsSuperUser]) 28 | def post_anime_opening_info( 29 | request: HttpRequest, 30 | entry: int | None = Form(...), 31 | name: str | None = Form(...), 32 | url: AnyUrl = Form(...), 33 | thumbnail: UploadedFile | None = File(...), 34 | ) -> AnimeOpeningModel: 35 | query = AnimeOpeningModel.objects.create( 36 | entry=entry, 37 | name=name, 38 | url=url, 39 | thumbnail=thumbnail, 40 | ) 41 | return query 42 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/anime/themes.py: -------------------------------------------------------------------------------- 1 | from apps.anime.models.anime_theme import AnimeThemeModel 2 | from apps.api.auth import AuthBearer 3 | from apps.api.decorator import permission_required 4 | from apps.api.permissions import IsSuperUser 5 | from apps.api.http import HttpRequest 6 | from ninja import Query, Router 7 | 8 | from ...filters.themes import ThemeFilter 9 | from ...schemas.anime.anime_theme import AnimeThemeGETSchema, AnimeThemePOSTSchema 10 | 11 | router = Router() 12 | 13 | 14 | @router.get("/themes", response=list[AnimeThemeGETSchema]) 15 | def get_anime_theme_info( 16 | request: HttpRequest, 17 | filters: ThemeFilter = Query(...), 18 | ) -> list[AnimeThemeModel]: 19 | query = AnimeThemeModel.objects.filter( 20 | type__icontains="anime", 21 | **filters.dict(exclude_none=True), 22 | ) 23 | return query 24 | 25 | 26 | @router.post("/themes", response=list[AnimeThemeGETSchema], auth=AuthBearer()) 27 | @permission_required([IsSuperUser]) 28 | def post_anime_theme_info( 29 | request: HttpRequest, 30 | payload: list[AnimeThemePOSTSchema], 31 | ) -> list[AnimeThemeModel]: 32 | instance_objects = [] 33 | for object in payload: 34 | instance_objects.append( 35 | AnimeThemeModel( 36 | type="anime", 37 | **object.dict(exclude_none=True), 38 | ) 39 | ) 40 | 41 | query = AnimeThemeModel.objects.bulk_create(instance_objects) 42 | return query 43 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/user/__init__.py: -------------------------------------------------------------------------------- 1 | from apps.user.models import CustomUser 2 | from apps.api.http import HttpRequest 3 | from django.shortcuts import get_object_or_404 4 | from ninja import File, Form, Router, UploadedFile 5 | from pydantic import AnyUrl, EmailStr 6 | 7 | from ...auth import AuthBearer 8 | from ...schemas.user import UserSchema 9 | 10 | router = Router() 11 | 12 | 13 | @router.get("/", response=UserSchema, auth=AuthBearer()) 14 | def get_user_info(request: HttpRequest) -> CustomUser: 15 | user = CustomUser.objects.get(pk=request.auth.id) 16 | return user 17 | 18 | 19 | @router.patch("/", response=UserSchema, auth=AuthBearer()) 20 | def patch_individual_user_info( 21 | request: HttpRequest, 22 | username: str = Form(...), 23 | first_name: str = Form(...), 24 | last_name: str = Form(...), 25 | password: str = Form(...), 26 | email: EmailStr = Form(...), 27 | avatar_provider: AnyUrl = Form(...), 28 | avatar: UploadedFile = File(...), 29 | ) -> None: 30 | pass 31 | 32 | 33 | @router.get("/{str:username}/", response=UserSchema) 34 | def get_individual_user_info( 35 | request: HttpRequest, 36 | username: str, 37 | ) -> CustomUser: 38 | user = get_object_or_404( 39 | CustomUser, 40 | username=username, 41 | ) 42 | return user 43 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/user/login.py: -------------------------------------------------------------------------------- 1 | from apps.api.models import Token 2 | from apps.user.backends import EmailOrUsernameModelBackend 3 | from django.http import Http404, HttpRequest 4 | from django_ratelimit.decorators import ratelimit 5 | from ninja import Form, Router 6 | from pydantic import EmailStr 7 | 8 | from ...schemas.user.login import LoginSchema 9 | 10 | router = Router() 11 | 12 | 13 | @router.post("/login", response=LoginSchema) 14 | @ratelimit(key="ip", rate="1/s") 15 | def post_user_login_info( 16 | request: HttpRequest, 17 | username: str | EmailStr = Form(...), 18 | password: str = Form(...), 19 | ) -> Token: 20 | user = EmailOrUsernameModelBackend.get_user_given_username_and_password( 21 | username, 22 | password, 23 | ) 24 | 25 | if not user: 26 | raise Http404("No such user exists") 27 | 28 | token, _ = Token.objects.get_or_create(user=user) 29 | return token 30 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/user/logout.py: -------------------------------------------------------------------------------- 1 | from apps.api.models import Token 2 | from django.http import HttpRequest, HttpResponse 3 | from ninja import Router 4 | from http import HTTPStatus 5 | from ...auth import AuthBearer 6 | 7 | router = Router() 8 | 9 | 10 | @router.delete("/logout", auth=AuthBearer()) 11 | def post_user_logout_info(request: HttpRequest) -> HttpResponse: 12 | token: Token = Token.objects.get(user=request.auth) 13 | token.delete() 14 | return HttpResponse("Successful", status=HTTPStatus.ACCEPTED) 15 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/user/signup.py: -------------------------------------------------------------------------------- 1 | from apps.user.models import CustomUser 2 | from django.conf import settings 3 | from apps.api.http import HttpRequest 4 | from ninja import Form, Router 5 | from pydantic import EmailStr 6 | 7 | from ...schemas.user import UserSchema 8 | 9 | router = Router() 10 | 11 | 12 | @router.post("/signup", response=UserSchema) 13 | def post_user_signup_info( 14 | request: HttpRequest, 15 | username: str = Form( 16 | ..., 17 | regex=rf"^[a-zA-Z0-9_-]+#[0-9]{{{settings.DISCRIMINATOR_LENGTH}}}$", 18 | ), 19 | password: str = Form(...), 20 | email: EmailStr = Form(...), 21 | ) -> CustomUser: 22 | user = CustomUser.objects.create_user( 23 | email=email, 24 | password=password, 25 | username=username, 26 | ) 27 | 28 | return user 29 | -------------------------------------------------------------------------------- /backend/django_core/apps/api/views/user/username_validity.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpRequest, HttpResponse 2 | from ninja import Form, Router 3 | 4 | from ...schemas.user.username_validity import UsernameValiditySchema 5 | 6 | router = Router() 7 | 8 | 9 | @router.post( 10 | "", 11 | response={ 12 | 302: UsernameValiditySchema, 13 | 404: UsernameValiditySchema, 14 | 406: UsernameValiditySchema, 15 | }, 16 | ) 17 | def post_username_validity_info( 18 | request: HttpRequest, 19 | username: str = Form(...), 20 | ) -> HttpResponse: 21 | pass 22 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/apps/characters/__init__.py -------------------------------------------------------------------------------- /backend/django_core/apps/characters/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import CharacterModel 4 | 5 | # Register your models here. 6 | 7 | 8 | @admin.register(CharacterModel) 9 | class CharacterAdmin(admin.ModelAdmin[CharacterModel]): 10 | search_fields = [ 11 | "name", 12 | "name_kanji", 13 | ] 14 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CharactersConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.characters" 7 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/migrations/0002_charactermodel_created_at_charactermodel_updated_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:13 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("characters", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="charactermodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | migrations.AddField( 22 | model_name="charactermodel", 23 | name="updated_at", 24 | field=models.DateTimeField(auto_now=True), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/migrations/0003_charactermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-24 07:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("characters", "0002_charactermodel_created_at_charactermodel_updated_at"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="charactermodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | preserve_default=False, 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/migrations/0004_alter_charactermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-25 19:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("characters", "0003_charactermodel_is_locked"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="charactermodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/migrations/0005_alter_charactermodel_created_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 05:51 2 | 3 | import django.db.models.functions.datetime 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("characters", "0004_alter_charactermodel_is_locked"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="charactermodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | db_default=django.db.models.functions.datetime.Now() 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/migrations/0006_alter_charactermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 09:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("characters", "0005_alter_charactermodel_created_at"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="charactermodel", 14 | name="is_locked", 15 | field=models.BooleanField(db_default=models.Value(False)), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/migrations/0007_alter_charactermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-21 03:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("characters", "0006_alter_charactermodel_is_locked"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="charactermodel", 15 | name="is_locked", 16 | field=models.BooleanField(db_default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/django_core/apps/characters/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/apps/characters/migrations/__init__.py -------------------------------------------------------------------------------- /backend/django_core/apps/characters/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from dynamic_filenames import FilePattern 3 | from mixins.models.created_at import CreatedAtMixin 4 | from mixins.models.is_locked import IsLockedMixin 5 | from mixins.models.updated_at import UpdatedAtMixin 6 | 7 | anime_charaters_pattern = FilePattern(filename_pattern="characters/{uuid:s}{ext}") 8 | 9 | 10 | # Create your models here. 11 | 12 | 13 | class CharacterModel(CreatedAtMixin, UpdatedAtMixin, IsLockedMixin): 14 | mal_id = models.IntegerField(unique=True, null=True) 15 | kitsu_id = models.IntegerField(unique=True, null=True) 16 | anilist_id = models.IntegerField(unique=True, null=True) 17 | 18 | name = models.CharField(max_length=1024) 19 | name_kanji = models.CharField(max_length=1024, null=True, blank=True) 20 | character_image = models.ImageField( 21 | upload_to=anime_charaters_pattern, 22 | default=None, 23 | blank=True, 24 | null=True, 25 | ) 26 | about = models.TextField(null=True, blank=True) 27 | 28 | def __str__(self) -> str: 29 | return f"{self.pk}. {self.name}" 30 | 31 | class Meta: 32 | verbose_name = "Character" 33 | verbose_name_plural = "Characters" 34 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/apps/comments/__init__.py -------------------------------------------------------------------------------- /backend/django_core/apps/comments/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import CommentModel 4 | 5 | 6 | # Register your models here. 7 | @admin.register(CommentModel) 8 | class CommentAdmin(admin.ModelAdmin[CommentModel]): 9 | autocomplete_fields = ["user"] 10 | list_filters = ["user"] 11 | search_fields = ["user__username", "text"] 12 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommentsConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.comments" 7 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/migrations/0002_alter_commentmodel_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0rc1 on 2023-12-05 13:22 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("comments", "0001_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="commentmodel", 14 | options={"verbose_name": "Comment", "verbose_name_plural": "Comments"}, 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/migrations/0003_commentmodel_dislikes_commentmodel_likes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0rc1 on 2023-12-05 18:45 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("comments", "0002_alter_commentmodel_options"), 10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="commentmodel", 16 | name="dislikes", 17 | field=models.ManyToManyField( 18 | blank=True, default=[], related_name="dislikes", to=settings.AUTH_USER_MODEL 19 | ), 20 | ), 21 | migrations.AddField( 22 | model_name="commentmodel", 23 | name="likes", 24 | field=models.ManyToManyField( 25 | blank=True, default=[], related_name="likes", to=settings.AUTH_USER_MODEL 26 | ), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/migrations/0004_alter_commentmodel_dislikes_alter_commentmodel_likes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0 on 2023-12-06 03:56 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("comments", "0003_commentmodel_dislikes_commentmodel_likes"), 10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="commentmodel", 16 | name="dislikes", 17 | field=models.ManyToManyField( 18 | blank=True, related_name="dislikes", to=settings.AUTH_USER_MODEL 19 | ), 20 | ), 21 | migrations.AlterField( 22 | model_name="commentmodel", 23 | name="likes", 24 | field=models.ManyToManyField( 25 | blank=True, related_name="likes", to=settings.AUTH_USER_MODEL 26 | ), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/migrations/0005_remove_commentmodel_dislikes_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0 on 2023-12-07 16:37 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("comments", "0004_alter_commentmodel_dislikes_alter_commentmodel_likes"), 10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name="commentmodel", 16 | name="dislikes", 17 | ), 18 | migrations.RemoveField( 19 | model_name="commentmodel", 20 | name="likes", 21 | ), 22 | migrations.AddField( 23 | model_name="commentmodel", 24 | name="downvotes", 25 | field=models.ManyToManyField( 26 | blank=True, related_name="downvotes", to=settings.AUTH_USER_MODEL 27 | ), 28 | ), 29 | migrations.AddField( 30 | model_name="commentmodel", 31 | name="upvotes", 32 | field=models.ManyToManyField( 33 | blank=True, related_name="upvotes", to=settings.AUTH_USER_MODEL 34 | ), 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/migrations/0006_commentmodel_deleted.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-23 15:09 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("comments", "0005_remove_commentmodel_dislikes_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="commentmodel", 15 | name="deleted", 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/migrations/0007_alter_commentmodel_user.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-23 15:12 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("comments", "0006_commentmodel_deleted"), 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name="commentmodel", 18 | name="user", 19 | field=models.ForeignKey( 20 | blank=True, 21 | null=True, 22 | on_delete=django.db.models.deletion.CASCADE, 23 | to=settings.AUTH_USER_MODEL, 24 | ), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/apps/comments/migrations/__init__.py -------------------------------------------------------------------------------- /backend/django_core/apps/comments/models.py: -------------------------------------------------------------------------------- 1 | from apps.user.models import CustomUser 2 | from django.contrib.postgres import indexes as idx 3 | from django.db import models 4 | from django_ltree.models import TreeModel 5 | from mixins.models.created_at import CreatedAtMixin 6 | 7 | 8 | # Create your models here. 9 | 10 | 11 | class CommentModel(CreatedAtMixin, TreeModel): 12 | user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True, blank=True) 13 | text = models.TextField() 14 | 15 | upvotes = models.ManyToManyField(CustomUser, blank=True, related_name="upvotes") 16 | downvotes = models.ManyToManyField(CustomUser, blank=True, related_name="downvotes") 17 | 18 | deleted = models.BooleanField(default=False) 19 | 20 | @property 21 | def ratio(self) -> int: 22 | # Formula : - 23 | return self.upvotes.count() - self.downvotes.count() 24 | 25 | @property 26 | def childrens(self) -> int: 27 | return self.children().count() 28 | 29 | def __str__(self) -> str: 30 | return f"{self.user} | {self.text}" 31 | 32 | class Meta: 33 | # https://youtu.be/u8F7bTJVe_4?t=1051 34 | indexes = [idx.GistIndex(fields=["path"])] 35 | verbose_name = "Comment" 36 | verbose_name_plural = "Comments" 37 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/tests.py: -------------------------------------------------------------------------------- 1 | # Create your tests here. 2 | -------------------------------------------------------------------------------- /backend/django_core/apps/comments/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/apps/episodes/__init__.py -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/admin.py: -------------------------------------------------------------------------------- 1 | # Register your models here. 2 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from ..forms import EpisodeAdminModelForm 4 | from ..models import EpisodeModel 5 | 6 | # Register your models here. 7 | 8 | 9 | @admin.register(EpisodeModel) 10 | class EpisodeAdmin(admin.ModelAdmin[EpisodeModel]): 11 | form = EpisodeAdminModelForm 12 | search_fields = ["episode_name"] 13 | 14 | 15 | from .episode_timestamp import EpisodeTimestampAdmin as EpisodeTimestampAdmin 16 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/admin/episode_timestamp.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.db.models.query import QuerySet 3 | from apps.api.http import HttpRequest 4 | 5 | from ..models.episode_timestamp import EpisodeTimestampModel 6 | 7 | # Register your models here. 8 | 9 | 10 | @admin.register(EpisodeTimestampModel) 11 | class EpisodeTimestampAdmin(admin.ModelAdmin[EpisodeTimestampModel]): 12 | autocomplete_fields = ["user"] 13 | list_filter = ["user"] 14 | search_fields = ["user__username"] 15 | 16 | def get_search_results( 17 | self, 18 | request: HttpRequest, 19 | queryset: QuerySet[EpisodeTimestampModel], 20 | search_term: str, 21 | ) -> tuple[QuerySet[EpisodeTimestampModel], bool]: 22 | queryset, may_have_duplicates = super().get_search_results( 23 | request, 24 | queryset, 25 | search_term, 26 | ) 27 | if "#" in search_term: 28 | queryset = self.model.objects.filter( 29 | user__username__in=[ 30 | # Remove trailing whitespace 31 | # We might have something like 32 | # user = ['baseplate-admin ', ' baseplate-foot'] 33 | # make it 34 | # user = ['baseplate-admin','baseplate-foot'] 35 | item.strip() 36 | for item in search_term.split(",") 37 | ] 38 | ).distinct() 39 | 40 | return queryset, may_have_duplicates 41 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class EpisodesConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.episodes" 7 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django_hstore_widget.forms import HStoreFormField 3 | 4 | from .models import EpisodeModel 5 | 6 | 7 | class EpisodeAdminModelForm(forms.ModelForm[EpisodeModel]): 8 | providers = HStoreFormField() 9 | 10 | class Meta: 11 | model = EpisodeModel 12 | fields = "__all__" 13 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/0002_episodemodel_episode_type.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-21 14:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("episodes", "0001_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="episodemodel", 14 | name="episode_type", 15 | field=models.CharField( 16 | blank=True, choices=[("sub", "sub"), ("dub", "dub")], max_length=3 17 | ), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/0004_episodemodel_created_at_episodemodel_updated_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:16 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("episodes", "0003_alter_episodecommentmodel_options_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="episodemodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | migrations.AddField( 22 | model_name="episodemodel", 23 | name="updated_at", 24 | field=models.DateTimeField(auto_now=True), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/0005_remove_episodecommentmodel_comment_added_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:48 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("episodes", "0004_episodemodel_created_at_episodemodel_updated_at"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name="episodecommentmodel", 15 | name="comment_added", 16 | ), 17 | migrations.AddField( 18 | model_name="episodecommentmodel", 19 | name="created_at", 20 | field=models.DateTimeField( 21 | auto_now_add=True, default=django.utils.timezone.now 22 | ), 23 | preserve_default=False, 24 | ), 25 | migrations.AddField( 26 | model_name="episodetimestampmodel", 27 | name="created_at", 28 | field=models.DateTimeField( 29 | auto_now_add=True, default=django.utils.timezone.now 30 | ), 31 | preserve_default=False, 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/0007_remove_episodetimestampmodel_episode.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.4 on 2023-08-31 01:56 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("episodes", "0006_alter_episodecommentmodel_options_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="episodetimestampmodel", 14 | name="episode", 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/0008_alter_episodecommentmodel_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.4 on 2023-08-31 01:58 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("episodes", "0007_remove_episodetimestampmodel_episode"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="episodecommentmodel", 14 | options={ 15 | "verbose_name": "Episode Comment", 16 | "verbose_name_plural": "Episode Comments", 17 | }, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/0009_alter_episodecommentmodel_created_at_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 05:51 2 | 3 | import django.db.models.functions.datetime 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("episodes", "0008_alter_episodecommentmodel_options"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="episodecommentmodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | db_default=django.db.models.functions.datetime.Now() 18 | ), 19 | ), 20 | migrations.AlterField( 21 | model_name="episodemodel", 22 | name="created_at", 23 | field=models.DateTimeField( 24 | db_default=django.db.models.functions.datetime.Now() 25 | ), 26 | ), 27 | migrations.AlterField( 28 | model_name="episodetimestampmodel", 29 | name="created_at", 30 | field=models.DateTimeField( 31 | db_default=django.db.models.functions.datetime.Now() 32 | ), 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/0010_alter_episodemodel_episode_comments_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0rc1 on 2023-12-05 13:07 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("comments", "0001_initial"), 9 | ("episodes", "0009_alter_episodecommentmodel_created_at_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="episodemodel", 15 | name="episode_comments", 16 | field=models.ManyToManyField(blank=True, to="comments.commentmodel"), 17 | ), 18 | migrations.DeleteModel( 19 | name="EpisodeCommentModel", 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/apps/episodes/migrations/__init__.py -------------------------------------------------------------------------------- /backend/django_core/apps/episodes/models/episode_timestamp.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.db import models 3 | from mixins.models.created_at import CreatedAtMixin 4 | 5 | # Create your models here. 6 | 7 | 8 | class EpisodeTimestampModel(CreatedAtMixin): 9 | timestamp = models.IntegerField(default=0) 10 | 11 | user = models.ForeignKey( 12 | get_user_model(), 13 | on_delete=models.CASCADE, 14 | ) 15 | 16 | def __str__(self) -> str: 17 | return f"{self.user} | {self.timestamp} seconds" 18 | 19 | class Meta: 20 | verbose_name = "Episode Timestamp" 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/apps/producers/__init__.py -------------------------------------------------------------------------------- /backend/django_core/apps/producers/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import ProducerModel 4 | 5 | # Register your models here. 6 | 7 | 8 | @admin.register(ProducerModel) 9 | class ProducerAdmin(admin.ModelAdmin[ProducerModel]): 10 | search_fields = ["name"] 11 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ProducersConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.producers" 7 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0002_producermodel_created_at_producermodel_updated_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:16 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("producers", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="producermodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | migrations.AddField( 22 | model_name="producermodel", 23 | name="updated_at", 24 | field=models.DateTimeField(auto_now=True), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0003_remove_producermodel_default_title.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-19 09:43 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("producers", "0002_producermodel_created_at_producermodel_updated_at"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="producermodel", 14 | name="default_title", 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0004_rename_japanese_title_producermodel_name_japanese.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-19 11:24 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("producers", "0003_remove_producermodel_default_title"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RenameField( 13 | model_name="producermodel", 14 | old_name="japanese_title", 15 | new_name="name_japanese", 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0005_producermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-24 07:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("producers", "0004_rename_japanese_title_producermodel_name_japanese"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="producermodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | preserve_default=False, 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0006_alter_producermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-25 19:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("producers", "0005_producermodel_is_locked"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="producermodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0007_alter_producermodel_created_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 05:51 2 | 3 | import django.db.models.functions.datetime 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("producers", "0006_alter_producermodel_is_locked"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="producermodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | db_default=django.db.models.functions.datetime.Now() 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0008_alter_producermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 09:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("producers", "0007_alter_producermodel_created_at"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="producermodel", 14 | name="is_locked", 15 | field=models.BooleanField(db_default=models.Value(False)), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/0009_alter_producermodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-21 03:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("producers", "0008_alter_producermodel_is_locked"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="producermodel", 15 | name="is_locked", 16 | field=models.BooleanField(db_default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/django_core/apps/producers/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/apps/producers/migrations/__init__.py -------------------------------------------------------------------------------- /backend/django_core/apps/producers/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from mixins.models.created_at import CreatedAtMixin 3 | from mixins.models.is_locked import IsLockedMixin 4 | from mixins.models.updated_at import UpdatedAtMixin 5 | 6 | # Create your models here. 7 | 8 | 9 | class ProducerModel(CreatedAtMixin, UpdatedAtMixin, IsLockedMixin): 10 | mal_id = models.IntegerField(unique=True, blank=True, null=True) 11 | kitsu_id = models.IntegerField(unique=True, blank=True, null=True) 12 | name = models.CharField( 13 | max_length=128, 14 | default="", 15 | null=False, 16 | blank=False, 17 | ) 18 | # Titles 19 | name_japanese = models.CharField( 20 | max_length=128, 21 | default="", 22 | null=True, 23 | blank=True, 24 | ) 25 | established = models.DateTimeField(null=True, blank=True) 26 | about = models.TextField(null=True, blank=True) 27 | 28 | def __str__(self) -> str: 29 | return f"{self.pk}. {self.name} | Mal_id : {self.mal_id}" 30 | 31 | class Meta: 32 | verbose_name = "Producer" 33 | verbose_name_plural = "Producers" 34 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/apps/staffs/__init__.py -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import StaffAlternateNameModel, StaffModel 4 | 5 | # Register your models here. 6 | 7 | 8 | @admin.register(StaffModel) 9 | class StaffModelAdmin(admin.ModelAdmin[StaffModel]): 10 | search_fields = [ 11 | "name", 12 | "given_name", 13 | "family_name", 14 | "alternate_names__name", 15 | ] 16 | 17 | 18 | @admin.register(StaffAlternateNameModel) 19 | class StaffAlternateNameAdmin(admin.ModelAdmin[StaffAlternateNameModel]): 20 | pass 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PeopleConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.staffs" 7 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0002_alter_staffalternatenamemodel_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-12 16:03 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("staffs", "0001_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterModelOptions( 13 | name="staffalternatenamemodel", 14 | options={ 15 | "verbose_name": "Staff | People ( Alternate Name )", 16 | "verbose_name_plural": "Staff | People ( Alternate Names )", 17 | }, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0003_staffmodel_created_at_staffmodel_updated_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:16 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("staffs", "0002_alter_staffalternatenamemodel_options"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="staffmodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | migrations.AddField( 22 | model_name="staffmodel", 23 | name="updated_at", 24 | field=models.DateTimeField(auto_now=True), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0004_staffmodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-24 07:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("staffs", "0003_staffmodel_created_at_staffmodel_updated_at"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="staffmodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | preserve_default=False, 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0005_alter_staffmodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-25 19:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("staffs", "0004_staffmodel_is_locked"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="staffmodel", 14 | name="is_locked", 15 | field=models.BooleanField(default=False), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0006_staffalternatenamemodel_staff_alternate_idx_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.4 on 2023-08-29 04:05 2 | 3 | import django.contrib.postgres.indexes 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("staffs", "0005_alter_staffmodel_is_locked"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddIndex( 14 | model_name="staffalternatenamemodel", 15 | index=django.contrib.postgres.indexes.GinIndex( 16 | fields=["name"], name="staff_alternate_idx", opclasses=["gin_trgm_ops"] 17 | ), 18 | ), 19 | migrations.AddIndex( 20 | model_name="staffmodel", 21 | index=django.contrib.postgres.indexes.GinIndex( 22 | fields=["name", "given_name", "family_name"], 23 | name="staff_name_idx", 24 | opclasses=["gin_trgm_ops", "gin_trgm_ops", "gin_trgm_ops"], 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0007_alter_staffmodel_created_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 05:51 2 | 3 | import django.db.models.functions.datetime 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("staffs", "0006_staffalternatenamemodel_staff_alternate_idx_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="staffmodel", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | db_default=django.db.models.functions.datetime.Now() 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0008_alter_staffmodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0b1 on 2023-10-29 09:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("staffs", "0007_alter_staffmodel_created_at"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="staffmodel", 14 | name="is_locked", 15 | field=models.BooleanField(db_default=models.Value(False)), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/0009_alter_staffmodel_is_locked.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-21 03:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("staffs", "0008_alter_staffmodel_is_locked"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="staffmodel", 15 | name="is_locked", 16 | field=models.BooleanField(db_default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/django_core/apps/staffs/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/apps/staffs/migrations/__init__.py -------------------------------------------------------------------------------- /backend/django_core/apps/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/apps/user/__init__.py -------------------------------------------------------------------------------- /backend/django_core/apps/user/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UserConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.user" 7 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.forms import UserChangeForm, UserCreationForm 2 | 3 | from .models import CustomUser 4 | 5 | 6 | class CustomUserCreationForm(UserCreationForm["CustomUser"]): 7 | class Meta: 8 | model = CustomUser 9 | fields = ("email",) 10 | 11 | 12 | class CustomUserChangeForm(UserChangeForm["CustomUser"]): 13 | class Meta: 14 | model = CustomUser 15 | fields = ("email",) 16 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0002_alter_token_unique_together.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-09 03:14 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("user", "0001_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterUniqueTogether( 13 | name="token", 14 | unique_together={("token", "user")}, 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0003_delete_token.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-14 01:24 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("user", "0002_alter_token_unique_together"), 9 | ] 10 | 11 | operations = [ 12 | migrations.DeleteModel( 13 | name="Token", 14 | ), 15 | ] 16 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0004_alter_customuser_avatar.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-21 08:52 2 | 3 | import dynamic_filenames 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("user", "0003_delete_token"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="customuser", 15 | name="avatar", 16 | field=models.ImageField( 17 | blank=True, 18 | default=None, 19 | null=True, 20 | upload_to=dynamic_filenames.FilePattern( 21 | filename_pattern="avatar/{uuid:s}{ext}" 22 | ), 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0005_remove_customuser_ip.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.7 on 2023-03-26 16:34 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("user", "0004_alter_customuser_avatar"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="customuser", 14 | name="ip", 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0006_customuser_created_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2023-04-13 14:18 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("user", "0005_remove_customuser_ip"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="customuser", 15 | name="created_at", 16 | field=models.DateTimeField( 17 | auto_now_add=True, default=django.utils.timezone.now 18 | ), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0008_alter_customuser_username.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-15 01:53 2 | 3 | import apps.user.validators.username 4 | import django.core.validators 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [ 10 | ("user", "0007_alter_customuser_unique_together_and_more"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="customuser", 16 | name="username", 17 | field=models.CharField( 18 | error_messages={"unique": "A user with that username already exists."}, 19 | help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", 20 | max_length=150, 21 | unique=True, 22 | validators=[ 23 | apps.user.validators.username.username_validator, 24 | django.core.validators.RegexValidator("^[a-zA-Z0-9_-]+#[0-9]{4}$"), 25 | ], 26 | verbose_name="username", 27 | ), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0008_remove_customuser_created_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.5 on 2023-09-09 04:19 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("user", "0007_alter_customuser_unique_together_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="customuser", 14 | name="created_at", 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0009_merge_20240317_1131.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0 on 2024-03-17 05:31 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('user', '0008_alter_customuser_username'), 10 | ('user', '0008_remove_customuser_created_at'), 11 | ] 12 | 13 | operations = [ 14 | ] 15 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0010_customuser_created_at_alter_customuser_username.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.4 on 2024-04-30 12:54 2 | 3 | import apps.user.validators.username 4 | import django.core.validators 5 | import django.db.models.functions.datetime 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('user', '0009_merge_20240317_1131'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='customuser', 18 | name='created_at', 19 | field=models.DateTimeField(db_default=django.db.models.functions.datetime.Now()), 20 | ), 21 | migrations.AlterField( 22 | model_name='customuser', 23 | name='username', 24 | field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[apps.user.validators.username.username_validator, django.core.validators.RegexValidator('^[a-zA-Z0-9_-]+#[0-9]{4}$', message='Username is not valid for this regex `^[a-zA-Z0-9_-]+#[0-9]{4}$`')], verbose_name='username'), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/0011_remove_customuser_created_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1 on 2024-09-01 13:03 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('user', '0010_customuser_created_at_alter_customuser_username'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='customuser', 15 | name='created_at', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/apps/user/migrations/__init__.py -------------------------------------------------------------------------------- /backend/django_core/apps/user/tests.py: -------------------------------------------------------------------------------- 1 | # Create your tests here. 2 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("avatar/", views.avatar_view, name="avatar_view"), 7 | ] 8 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/validators/__init__.py: -------------------------------------------------------------------------------- 1 | from .username import username_validator as username_validator 2 | -------------------------------------------------------------------------------- /backend/django_core/apps/user/validators/username.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import ValidationError 2 | 3 | INVALID_USERNAMES = { 4 | "admin", 5 | } 6 | 7 | 8 | # This will only get invoked when using modelform 9 | def username_validator(username: str) -> None: 10 | if username in INVALID_USERNAMES: 11 | raise ValidationError(f"Username {username} not allowed") 12 | -------------------------------------------------------------------------------- /backend/django_core/core/__init__.py: -------------------------------------------------------------------------------- 1 | from .celery import app as celery_app 2 | 3 | __all__ = ("celery_app",) 4 | -------------------------------------------------------------------------------- /backend/django_core/core/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for core project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /backend/django_core/core/storages.py: -------------------------------------------------------------------------------- 1 | from django.core.files.storage import FileSystemStorage 2 | 3 | 4 | class OverwriteStorage(FileSystemStorage): 5 | # https://stackoverflow.com/questions/9522759/imagefield-overwrite-image-file-with-same-name 6 | def get_available_name( 7 | self, 8 | name: str, 9 | max_length: int | None = None, 10 | ) -> str: 11 | self.delete(name) 12 | return super().get_available_name( 13 | name, 14 | max_length, 15 | ) 16 | -------------------------------------------------------------------------------- /backend/django_core/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | 4 | import os 5 | import sys 6 | 7 | 8 | def main() -> None: 9 | """Run administrative tasks.""" 10 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings") 11 | try: 12 | from django.core.management import execute_from_command_line 13 | except ImportError as exc: 14 | raise ImportError( 15 | "Couldn't import Django. Are you sure it's installed and " 16 | "available on your PYTHONPATH environment variable? Did you " 17 | "forget to activate a virtual environment?" 18 | ) from exc 19 | execute_from_command_line(sys.argv) 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /backend/django_core/media/anime/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/media/avatars/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/media/banner/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/media/characters/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/media/cover/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/media/ending/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/media/episode/.gitignore: -------------------------------------------------------------------------------- 1 | *.mkv 2 | *.mp4 3 | *.ts 4 | -------------------------------------------------------------------------------- /backend/django_core/media/episode_cover/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/media/opening/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/media/staffs/.gitignore: -------------------------------------------------------------------------------- 1 | # JPEG 2 | *.jpg 3 | *.jpeg 4 | *.jpe 5 | *.jif 6 | *.jfif 7 | *.jfi 8 | 9 | # JPEG 2000 10 | *.jp2 11 | *.j2k 12 | *.jpf 13 | *.jpx 14 | *.jpm 15 | *.mj2 16 | 17 | # JPEG XR 18 | *.jxr 19 | *.hdp 20 | *.wdp 21 | 22 | # Graphics Interchange Format 23 | *.gif 24 | 25 | # RAW 26 | *.raw 27 | 28 | # Web P 29 | *.webp 30 | 31 | # Portable Network Graphics 32 | *.png 33 | 34 | # Animated Portable Network Graphics 35 | *.apng 36 | 37 | # Multiple-image Network Graphics 38 | *.mng 39 | 40 | # Tagged Image File Format 41 | *.tiff 42 | *.tif 43 | 44 | # Scalable Vector Graphics 45 | *.svg 46 | *.svgz 47 | 48 | # Portable Document Format 49 | *.pdf 50 | 51 | # X BitMap 52 | *.xbm 53 | 54 | # BMP 55 | *.bmp 56 | *.dib 57 | 58 | # ICO 59 | *.ico 60 | 61 | # 3D Images 62 | *.3dm 63 | *.max 64 | 65 | # AV1 Image File Format 66 | *.avif 67 | -------------------------------------------------------------------------------- /backend/django_core/mixins/models/created_at.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.db.models.functions import Now 3 | 4 | 5 | class CreatedAtMixin(models.Model): 6 | created_at = models.DateTimeField(db_default=Now()) 7 | 8 | class Meta: 9 | abstract = True 10 | -------------------------------------------------------------------------------- /backend/django_core/mixins/models/is_locked.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class IsLockedMixin(models.Model): 5 | is_locked = models.BooleanField(db_default=False) 6 | 7 | class Meta: 8 | abstract = True 9 | -------------------------------------------------------------------------------- /backend/django_core/mixins/models/updated_at.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class UpdatedAtMixin(models.Model): 5 | updated_at = models.DateTimeField(auto_now=True) 6 | 7 | class Meta: 8 | abstract = True 9 | -------------------------------------------------------------------------------- /backend/django_core/static_src/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/backend/django_core/static_src/.gitkeep -------------------------------------------------------------------------------- /backend/django_core/templates/admin/base.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base.html" %} 2 | {% load static %} 3 | {% block extrahead %} 4 | 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /backend/django_core/templates/anime/episode/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/django_core/templates/anime/explore/index.html: -------------------------------------------------------------------------------- 1 | {% block head %} 2 | Explore - AnimeCore 3 | {% endblock %} 4 | 5 | 6 | -------------------------------------------------------------------------------- /backend/django_core/templates/anime/index.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | {% block head %} 4 | AnimeCore 5 | {% endblock %} 6 | 7 | 12 | -------------------------------------------------------------------------------- /backend/django_core/templates/anime/info/index.html: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /backend/django_core/templates/errors/base.html: -------------------------------------------------------------------------------- 1 | {% extends 'tailwind_base.html' %} 2 | {% block title %}CoreProject | {{ error_status_code }}{% endblock %} 3 | {% block body %} 4 |
5 | 8 |
9 |

{{ error_status_code }} - {{ error_status }}

10 |

{{ error_text|safe }}

11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /backend/django_core/templates/home/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'tailwind_base.html' %} 2 | 3 | {% block head %} 4 | CoreProject - Imagine a platform 5 | {% endblock %} 6 | 7 | {%block body%} 8 | 9 | {% endblock%} -------------------------------------------------------------------------------- /backend/django_core/templates/upload/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'tailwind_base.html' %} 2 | 3 | {% block head %} 4 | Upload on AnimeCore 5 | {% endblock %} 6 | 7 | {%block body%} 8 | 9 | {% endblock%} -------------------------------------------------------------------------------- /backend/django_core/templates/user/_layout.html: -------------------------------------------------------------------------------- 1 | {% extends 'tailwind_base.html' %} 2 | {% load static %} 3 | 4 | {% block body %} 5 |
6 | 10 | 11 |
12 | {% block sidebody %}{% endblock %} 13 |
14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /backend/django_core/templates/user/login/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'user/_layout.html'%} 2 | 3 | {% block title %} 4 | Login | CoreProject 5 | {% endblock %} 6 | 7 | 8 | {% block sidebody %} 9 | 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /backend/django_core/templates/user/register/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'user/_layout.html'%} 2 | 3 | {% block title %} 4 | Register | CoreProject 5 | {% endblock %} 6 | 7 | {% block sidebody %} 8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /backend/django_core/templates/user/reset_password/index.html: -------------------------------------------------------------------------------- 1 | {% extends "user/_layout.html" %} 2 | 3 | {% block title %} 4 | Reset Password | CoreProject 5 | {% endblock %} 6 | 7 | {% block sidebody %} 8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /backend/django_core/templates/user/user_does_not_exist.php: -------------------------------------------------------------------------------- 1 | 12 | 13 | User not found {{ user_id }} 14 | -------------------------------------------------------------------------------- /backend/django_core/utilities/format.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | KOKORO_WORD_COLOR_MAP = { 4 | "-": "white", 5 | "a": "white", 6 | "c": "white", 7 | "h": "white", 8 | "k": "white", 9 | "n": "white", 10 | "o": "yellow", 11 | "r": "white", 12 | } 13 | 14 | TAILWIND_COLOR_MAP = { 15 | "white": "text-white", 16 | "yellow": "text-warning", 17 | } 18 | 19 | WORD = "kokoro-chan" 20 | 21 | 22 | def format_kokoro_color(input: str) -> str: 23 | KOKORO_REGEX = re.compile(rf"{WORD}", re.M) 24 | 25 | color_formated_string = "" 26 | 27 | # This variable has ['kokoro-chan'] 28 | re.findall(KOKORO_REGEX, input) 29 | 30 | # Parse each letter in the word 31 | for letter in WORD: 32 | color = KOKORO_WORD_COLOR_MAP[letter] 33 | color_class = TAILWIND_COLOR_MAP[color] 34 | color_formated_string += f"{letter}" 35 | 36 | # Color the font. 37 | text = re.sub(KOKORO_REGEX, color_formated_string, input) 38 | # Hyperlink the home 39 | return text 40 | -------------------------------------------------------------------------------- /backend/django_core/utilities/rgb_to_hex.py: -------------------------------------------------------------------------------- 1 | # https://www.educative.io/answers/how-to-convert-hex-to-rgb-and-rgb-to-hex-in-python 2 | 3 | 4 | def rgb_to_hex(red: int, green: int, blue: int) -> str: 5 | return ("{:X}{:X}{:X}").format(red, green, blue) 6 | -------------------------------------------------------------------------------- /discord/TODO.md: -------------------------------------------------------------------------------- 1 | Try to use Discord.py 2 | 3 | # Sync permissions from django to discord users 4 | 5 | | Django | Discord | 6 | | --------- | ------- | 7 | | Superuser | Admin | 8 | | Staff | Mod | 9 | -------------------------------------------------------------------------------- /discord/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/discord/__init__.py -------------------------------------------------------------------------------- /discord/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration with an async dbapi. 2 | -------------------------------------------------------------------------------- /discord/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade() -> None: 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade() -> None: 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /discord/database.py: -------------------------------------------------------------------------------- 1 | from settings import POSTGRES 2 | from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine 3 | from sqlalchemy.orm import declarative_base, sessionmaker 4 | 5 | NAME = POSTGRES["NAME"] 6 | USER = POSTGRES["USER"] 7 | PASSWORD = POSTGRES["PASSWORD"] 8 | HOST = POSTGRES.get("HOST") or "localhost" 9 | PORT = POSTGRES.get("PORT") or "5432" 10 | 11 | engine = create_async_engine( 12 | f"postgresql+asyncpg://{USER}:{PASSWORD}@{HOST}:{PORT}/{NAME}", echo=True 13 | ) 14 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, class_=AsyncSession) 15 | 16 | django_engine = create_async_engine( 17 | f"postgresql+asyncpg://{USER}:{PASSWORD}@{HOST}:{PORT}/django", echo=True 18 | ) 19 | DjangoSessionLocal = sessionmaker(autocommit=False, autoflush=False, class_=AsyncSession) 20 | 21 | BASE = declarative_base() 22 | -------------------------------------------------------------------------------- /discord/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/discord/models/.gitkeep -------------------------------------------------------------------------------- /discord/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "discord" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["baseplate-admin <61817579+baseplate-admin@users.noreply.github.com>"] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.11" 10 | discord-py = "^2.5.2" 11 | sqlalchemy = {extras = ["postgresql-asyncpg"], version = "^2.0.40"} 12 | aiohttp-jinja2 = "^1.6" 13 | alembic = "^1.15.2" 14 | 15 | 16 | [tool.poetry.group.dev.dependencies] 17 | black = "^25.1.0" 18 | aiohttp-devtools = "^1.1" 19 | poethepoet = "^0.33.1" 20 | 21 | [tool.poe.tasks] 22 | dev = 'adev runserver ./web/main.py --app-factory=aiohttp_app' 23 | 24 | [build-system] 25 | build-backend = "poetry.core.masonry.api" 26 | requires = [ 27 | "poetry-core", 28 | ] 29 | -------------------------------------------------------------------------------- /discord/settings.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | # Goes to the directory where `server.py` is present 4 | BASE_DIR = Path(__file__).resolve().parent 5 | TEMPLATE_DIRS = str( 6 | BASE_DIR.joinpath("templates"), 7 | ) 8 | 9 | # Goes to the directory where pipfile is present 10 | DJANGO_DIR = BASE_DIR.parent.joinpath("django_core") 11 | DJANGO_MEDIA_DIR = str(DJANGO_DIR.joinpath("media")) 12 | 13 | # Postgres 14 | POSTGRES = { 15 | "NAME": "discord-py", 16 | "USER": "postgres", 17 | "PASSWORD": "supersecretpassword", 18 | "HOST": "", 19 | "PORT": "", 20 | } 21 | -------------------------------------------------------------------------------- /discord/web/main.py: -------------------------------------------------------------------------------- 1 | from collections.abc import AsyncGenerator 2 | 3 | import aiohttp_jinja2 4 | import jinja2 5 | from aiohttp import web 6 | from database import DjangoSessionLocal, SessionLocal, engine 7 | from settings import TEMPLATE_DIRS 8 | 9 | routes = web.RouteTableDef() 10 | 11 | 12 | async def db_context( 13 | app: web.Application, 14 | ) -> AsyncGenerator[None, SessionLocal]: 15 | app["db"] = SessionLocal(bind=engine) 16 | yield 17 | await engine.dispose() 18 | 19 | 20 | async def django_db_context( 21 | app: web.Application, 22 | ) -> AsyncGenerator[None, SessionLocal]: 23 | app["django_db"] = DjangoSessionLocal(bind=engine) 24 | yield 25 | await engine.dispose() 26 | 27 | 28 | async def aiohttp_app() -> web.Application: 29 | app = web.Application() 30 | aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader(TEMPLATE_DIRS)) 31 | app.cleanup_ctx.append(db_context) 32 | app.cleanup_ctx.append(django_db_context) 33 | 34 | return app 35 | 36 | 37 | if __name__ == "__main__": 38 | web.run_app(aiohttp_app()) 39 | -------------------------------------------------------------------------------- /discord/web/template/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/discord/web/template/.gitkeep -------------------------------------------------------------------------------- /discord/web/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/discord/web/views/.gitkeep -------------------------------------------------------------------------------- /discord/web/views/login.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/discord/web/views/login.py -------------------------------------------------------------------------------- /seeder/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /seeder/.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: 'npm' 9 | directory: '/' 10 | schedule: 11 | interval: 'weekly' 12 | 13 | - package-ecosystem: 'pip' 14 | directory: '/' 15 | schedule: 16 | interval: 'weekly' 17 | -------------------------------------------------------------------------------- /seeder/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | # Editor directories and files 11 | .vscode/* 12 | !.vscode/extensions.json 13 | .idea 14 | .DS_Store 15 | *.suo 16 | *.ntvs* 17 | *.njsproj 18 | *.sln 19 | *.sw? 20 | 21 | # node 22 | node_modules 23 | dist-ssr 24 | build 25 | *.local 26 | 27 | # sveltekit 28 | dist-front 29 | .svelte-kit 30 | 31 | # poetry 32 | .venv 33 | 34 | # pyloid 35 | venv-pyloid 36 | __pycache__ 37 | 38 | # pyinstaller 39 | dist 40 | 41 | # npm 42 | package-lock.json 43 | 44 | # yarn 45 | yarn.lock 46 | 47 | # pnpm 48 | pnpm-lock.yaml 49 | 50 | # bun 51 | bun-lock.yaml 52 | 53 | # build.py handles that 54 | *.spec 55 | -------------------------------------------------------------------------------- /seeder/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | legacy-peer-deps=true 3 | -------------------------------------------------------------------------------- /seeder/.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /seeder/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "printWidth": 92, 5 | "tabWidth": 4, 6 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], 7 | "overrides": [ 8 | { 9 | "files": "*.svelte", 10 | "options": { 11 | "parser": "svelte" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /seeder/build.py: -------------------------------------------------------------------------------- 1 | from pyloid.builder import create_spec_from_json, build_from_spec, cleanup_before_build 2 | 3 | 4 | def main(): 5 | spec_path = create_spec_from_json("build_config.json") 6 | cleanup_before_build("build_config.json") 7 | build_from_spec(spec_path) 8 | 9 | if __name__ == "__main__": 10 | main() 11 | -------------------------------------------------------------------------------- /seeder/build_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/pyloid/pyloid/refs/heads/main/src/pyloid/builder/build_config.schema.json", 3 | "before_build": { 4 | "cleanup_patterns": [ 5 | "PySide6/opengl32sw.dll", 6 | "PySide6/translations/*.qm", 7 | "PySide6/translations/qtwebengine_locales/*.pak", 8 | "!PySide6/translations/qtwebengine_locales/en-US.pak" 9 | ] 10 | }, 11 | "name": "pyloid-app", 12 | "main_script": "run.py", 13 | "datas": [ 14 | ["src_pyloid/icons/", "src_pyloid/icons/"], 15 | ["dist-front/", "dist-front/"] 16 | ], 17 | "excludes": [ 18 | "PySide6.QtQml", 19 | "PySide6.QtTest", 20 | "PySide6.Qt3D", 21 | "PySide6.QtSensors", 22 | "PySide6.QtCharts", 23 | "PySide6.QtGraphs", 24 | "PySide6.QtDataVisualization", 25 | "PySide6.QtQuick", 26 | "PySide6.QtDesigner", 27 | "PySide6.QtUiTools", 28 | "PySide6.QtHelp" 29 | ], 30 | "icon": "src_pyloid/icons/icon.ico", 31 | "bundle": { 32 | "windows": "onefile", 33 | "macos": "app", 34 | "linux": "directory" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /seeder/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://next.shadcn-svelte.com/schema.json", 3 | "style": "default", 4 | "tailwind": { 5 | "config": "tailwind.config.ts", 6 | "css": "src\\app.css", 7 | "baseColor": "slate" 8 | }, 9 | "aliases": { 10 | "components": "$lib/components", 11 | "utils": "$lib/utils", 12 | "ui": "$lib/components/ui", 13 | "hooks": "$lib/hooks" 14 | }, 15 | "typescript": true, 16 | "registry": "https://next.shadcn-svelte.com/registry" 17 | } 18 | -------------------------------------------------------------------------------- /seeder/eslint.config.js: -------------------------------------------------------------------------------- 1 | import prettier from 'eslint-config-prettier'; 2 | import js from '@eslint/js'; 3 | import { includeIgnoreFile } from '@eslint/compat'; 4 | import svelte from 'eslint-plugin-svelte'; 5 | import globals from 'globals'; 6 | import { fileURLToPath } from 'node:url'; 7 | import ts from 'typescript-eslint'; 8 | const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); 9 | 10 | export default ts.config( 11 | includeIgnoreFile(gitignorePath), 12 | js.configs.recommended, 13 | ...ts.configs.recommended, 14 | ...svelte.configs['flat/recommended'], 15 | prettier, 16 | ...svelte.configs['flat/prettier'], 17 | { 18 | languageOptions: { 19 | globals: { 20 | ...globals.browser, 21 | ...globals.node 22 | } 23 | } 24 | }, 25 | { 26 | files: ['**/*.svelte'], 27 | 28 | languageOptions: { 29 | parserOptions: { 30 | parser: ts.parser 31 | } 32 | } 33 | } 34 | ); 35 | -------------------------------------------------------------------------------- /seeder/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /seeder/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "svelte_pyloid_boilerplate" 3 | version = "0.0.1" 4 | requires-python = ">=3.13,<3.14" 5 | dependencies = [ 6 | "pyloid>=0.20.2,<0.21", 7 | "websockets>=14.2", 8 | ] 9 | 10 | [dependency-groups] 11 | dev = [ 12 | "poethepoet>=0.32.1,<0.33", 13 | "black>=24.10.0,<25", 14 | "flake8>=7.1.1,<8", 15 | "pyinstaller>=6.11.1", 16 | "ruff>=0.9.2", 17 | ] 18 | 19 | [tool.uv] 20 | package = false 21 | 22 | [build-system] 23 | requires = ["hatchling"] 24 | build-backend = "hatchling.build" 25 | 26 | [tool.poe.tasks] 27 | dev = "python ./run.py" 28 | build = "python ./build.py" 29 | format = "black ./" 30 | lint = "flake8 ./" 31 | -------------------------------------------------------------------------------- /seeder/run.py: -------------------------------------------------------------------------------- 1 | from src_pyloid.main import app 2 | 3 | if __name__ == "__main__": 4 | app.run() 5 | -------------------------------------------------------------------------------- /seeder/scripts/replaceAssets.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A module to patch svelte's weird behavior 3 | * 4 | * @see https://github.com/sveltejs/kit/issues/9569#issuecomment-1767949037 5 | */ 6 | 7 | import fs from 'node:fs'; 8 | import path from 'node:path'; 9 | 10 | const readDirRecursive = async (filePath: string): Promise => { 11 | const dir = await fs.promises.readdir(filePath); 12 | const files = await Promise.all( 13 | dir.map(async (relativePath) => { 14 | const absolutePath = path.join(filePath, relativePath); 15 | const stat = await fs.promises.lstat(absolutePath); 16 | return stat.isDirectory() ? readDirRecursive(absolutePath) : absolutePath; 17 | }) 18 | ); 19 | return files.flat(); 20 | }; 21 | 22 | const files = await readDirRecursive('./dist-front'); 23 | if (files) { 24 | Array.from(files).forEach((file) => { 25 | if ( 26 | !( 27 | file.endsWith('.js') || 28 | file.endsWith('.html') || 29 | file.endsWith('.map') || 30 | file.endsWith('.css') 31 | ) 32 | ) { 33 | return; 34 | } 35 | fs.readFile(file, 'utf8', (_, data) => { 36 | fs.writeFile(file, data.replace(/http:\/\//g, '.'), 'utf8', () => { 37 | console.log("Wrote file '" + file + "'"); 38 | }); 39 | }); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /seeder/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://svelte.dev/docs/kit/types#app.d.ts 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /seeder/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /seeder/src/demo.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | describe('sum test', () => { 4 | it('adds 1 + 2 to equal 3', () => { 5 | expect(1 + 2).toBe(3); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /seeder/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | pyloid: any; 4 | } 5 | } 6 | 7 | export {}; 8 | -------------------------------------------------------------------------------- /seeder/src/lib/components/ui/button/index.ts: -------------------------------------------------------------------------------- 1 | import Root, { 2 | type ButtonProps, 3 | type ButtonSize, 4 | type ButtonVariant, 5 | buttonVariants, 6 | } from "./button.svelte"; 7 | 8 | export { 9 | Root, 10 | type ButtonProps as Props, 11 | // 12 | Root as Button, 13 | buttonVariants, 14 | type ButtonProps, 15 | type ButtonSize, 16 | type ButtonVariant, 17 | }; 18 | -------------------------------------------------------------------------------- /seeder/src/lib/components/ui/separator/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './separator.svelte'; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Separator 7 | }; 8 | -------------------------------------------------------------------------------- /seeder/src/lib/components/ui/separator/separator.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | -------------------------------------------------------------------------------- /seeder/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /seeder/src/lib/interface/pyloid.ts: -------------------------------------------------------------------------------- 1 | function require_pyloid() { 2 | return function ( 3 | _: any, 4 | __: string, 5 | descriptor: TypedPropertyDescriptor 6 | ): void | TypedPropertyDescriptor { 7 | const originalMethod = descriptor.value; 8 | 9 | if (typeof originalMethod !== 'function') { 10 | throw new Error('Decorator can only be applied to methods.'); 11 | } 12 | 13 | // Wrap the original method 14 | descriptor.value = function (...args: any[]) { 15 | try { 16 | if (window.pyloid !== undefined) { 17 | return originalMethod.apply(this, args); // Call the original method 18 | } else { 19 | console.warn('pyloid is undefined'); 20 | return null; 21 | } 22 | } catch (error) { 23 | console.error('Error in require_pyloid:', error); 24 | return null; 25 | } 26 | }; 27 | 28 | return descriptor; 29 | }; 30 | } 31 | 32 | export class JSApi { 33 | @require_pyloid() 34 | async get_server_port(): Promise<{ host: string; port: number } | null> { 35 | return await window.pyloid.JSApi.get_server_port(); 36 | } 37 | 38 | @require_pyloid() 39 | async start_websocket_server(): Promise { 40 | return await window.pyloid.JSApi.start_websocket_server(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /seeder/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /seeder/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 | {@render children()} 10 |
11 | -------------------------------------------------------------------------------- /seeder/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | {#if dev} 10 | {#if !(page_shown || links_shown)} 11 |
12 | 21 | 30 |
31 | {:else if links_shown} 32 |
33 | welcome 34 | server 35 |
36 | {/if} 37 | {/if} 38 | -------------------------------------------------------------------------------- /seeder/src/routes/server/+page.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | {#if host || port} 19 | Listening on {host}:{port} 20 | {:else} 21 | Is pyloid running? 22 | {/if} 23 | -------------------------------------------------------------------------------- /seeder/src/routes/welcome/+layout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | {@render children()} 8 |
9 | -------------------------------------------------------------------------------- /seeder/src/routes/welcome/+page.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |

Welcome to CoreProject seeder

13 |

What will this machine act like?

14 | 15 |
16 | {#each items as item} 17 | {@const is_last = item === items.at(-1)} 18 |
19 |
20 | 21 |
22 |
23 | {item.name} 24 |
25 |
26 | {#if !is_last} 27 | 28 | {/if} 29 | {/each} 30 |
31 | 32 | 37 | -------------------------------------------------------------------------------- /seeder/src_pyloid/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/seeder/src_pyloid/__init__.py -------------------------------------------------------------------------------- /seeder/src_pyloid/bridge/__init__.py: -------------------------------------------------------------------------------- 1 | from .server import Server 2 | 3 | 4 | class JSApi(Server): 5 | pass 6 | -------------------------------------------------------------------------------- /seeder/src_pyloid/bridge/server.py: -------------------------------------------------------------------------------- 1 | from pyloid import Bridge, PyloidAPI 2 | from ..server import WebSocketServer 3 | from ..functions.port import find_free_port 4 | 5 | 6 | class Server(PyloidAPI): 7 | def __init__(self): 8 | super().__init__() 9 | 10 | # Local states 11 | self.__port = find_free_port() 12 | 13 | @Bridge(result=dict) 14 | def get_server_port(self): 15 | return {"host": "localhost", "port": self.__port} 16 | 17 | @Bridge(result=str) 18 | def start_websocket_server(self): 19 | worker = WebSocketServer(self.__port) 20 | 21 | try: 22 | worker.start() 23 | print("Started websocket server") 24 | return "true" 25 | except Exception as e: 26 | return e 27 | -------------------------------------------------------------------------------- /seeder/src_pyloid/functions/__init__.py: -------------------------------------------------------------------------------- 1 | from .port import find_free_port as find_free_port 2 | -------------------------------------------------------------------------------- /seeder/src_pyloid/functions/port.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | 4 | def find_free_port(start_port=1024, end_port=65535): 5 | for port in range(start_port, end_port + 1): 6 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 7 | sock.settimeout(1) 8 | try: 9 | sock.bind(("localhost", port)) 10 | sock.close() 11 | return port # Port is available 12 | except socket.error: 13 | continue # Port is in use, try the next one 14 | return None # No free port found 15 | -------------------------------------------------------------------------------- /seeder/src_pyloid/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/seeder/src_pyloid/icons/icon.icns -------------------------------------------------------------------------------- /seeder/src_pyloid/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/seeder/src_pyloid/icons/icon.ico -------------------------------------------------------------------------------- /seeder/src_pyloid/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/seeder/src_pyloid/icons/icon.png -------------------------------------------------------------------------------- /seeder/src_pyloid/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pyloid import Pyloid, get_production_path, is_production 3 | from .bridge import JSApi 4 | 5 | 6 | app = Pyloid(app_name="Pyloid-App", single_instance=True) 7 | production_path = get_production_path() 8 | 9 | 10 | if is_production() and production_path: 11 | app.set_icon(os.path.join(production_path, "icons/icon.png")) 12 | app.set_tray_icon(os.path.join(production_path, "icons/icon.png")) 13 | else: 14 | app.set_icon("src-pyloid/icons/icon.png") 15 | app.set_tray_icon("src-pyloid/icons/icon.png") 16 | 17 | 18 | if is_production() and production_path: 19 | # production 20 | window = app.create_window( 21 | title="Pyloid Browser-production", 22 | js_apis=[JSApi()], 23 | dev_tools=True, 24 | ) 25 | window.load_file(os.path.join(production_path, "dist-front/index.html")) 26 | else: 27 | window = app.create_window( 28 | title="Pyloid Browser-dev", 29 | js_apis=[JSApi()], 30 | dev_tools=True, 31 | ) 32 | window.load_url("http://localhost:5173") 33 | 34 | window.show_and_focus() 35 | -------------------------------------------------------------------------------- /seeder/src_pyloid/managers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/seeder/src_pyloid/managers/__init__.py -------------------------------------------------------------------------------- /seeder/src_pyloid/server/__init__.py: -------------------------------------------------------------------------------- 1 | from .websocket import WebSocketServer as WebSocketServer 2 | -------------------------------------------------------------------------------- /seeder/src_pyloid/server/websocket.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtCore import QThread 2 | from websockets.sync.server import serve 3 | from typing import Optional 4 | 5 | 6 | class WebSocketServer(QThread): 7 | _instance: Optional["WebSocketServer"] = None 8 | 9 | def __init__(self, port: int): 10 | self.port = port 11 | super().__init__() 12 | 13 | def __new__(cls, *args, **kwargs): 14 | if cls._instance is None: 15 | cls._instance = super(WebSocketServer, cls).__new__(cls, *args, **kwargs) 16 | return cls._instance 17 | 18 | @staticmethod 19 | def echo(websocket): 20 | for message in websocket: 21 | websocket.send(message) 22 | 23 | def run(self): 24 | with serve(self.echo, "localhost", self.port) as server: 25 | server.serve_forever() 26 | -------------------------------------------------------------------------------- /seeder/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/seeder/static/favicon.png -------------------------------------------------------------------------------- /seeder/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-static'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://svelte.dev/docs/kit/integrations 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | compilerOptions: { 10 | // runes: true 11 | }, 12 | 13 | kit: { 14 | adapter: adapter({ 15 | pages: 'dist-front', 16 | strict: true, 17 | fallback: '404.html' 18 | }), 19 | paths: { 20 | assets: 'http://', 21 | relative: true 22 | }, 23 | router: { 24 | type: 'hash' 25 | }, 26 | alias: { 27 | // '@/*': './src/lib' 28 | } 29 | } 30 | }; 31 | 32 | export default config; 33 | -------------------------------------------------------------------------------- /seeder/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowImportingTsExtensions": true, 5 | "allowJs": true, 6 | "checkJs": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "resolveJsonModule": true, 10 | "skipLibCheck": true, 11 | "sourceMap": true, 12 | "strict": true, 13 | "moduleResolution": "bundler", 14 | "experimentalDecorators": true, 15 | "emitDecoratorMetadata": true 16 | } 17 | // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias 18 | // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files 19 | // 20 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 21 | // from the referenced tsconfig.json - TypeScript does not merge them in 22 | } 23 | -------------------------------------------------------------------------------- /seeder/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | import { sveltekit } from '@sveltejs/kit/vite'; 3 | 4 | export default defineConfig({ 5 | esbuild: { 6 | target: 'esnext', 7 | legalComments: 'external' 8 | }, 9 | css: { 10 | devSourcemap: true, 11 | // Switch to lightning.css when tailwind supports it 12 | transformer: 'postcss' 13 | }, 14 | build: { 15 | commonjsOptions: { 16 | transformMixedEsModules: true 17 | }, 18 | chunkSizeWarningLimit: 2048, 19 | emptyOutDir: true, 20 | target: 'esnext', 21 | cssTarget: 'esnext', 22 | minify: 'terser' 23 | //sourcemap: true 24 | }, 25 | worker: { 26 | format: 'es' 27 | }, 28 | 29 | plugins: [sveltekit()], 30 | 31 | test: { 32 | include: ['src/**/*.{test,spec}.{js,ts}'] 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /tracker/backend/.dockerignore: -------------------------------------------------------------------------------- 1 | **/*.egg-info/ 2 | tests/ 3 | 4 | 5 | # Exclude version control and IDE files 6 | .git/ 7 | .gitignore 8 | .idea/ 9 | .vscode/ 10 | 11 | # Ignore local virtual environments 12 | .venv/ 13 | venv/ 14 | env/ 15 | 16 | # Exclude build, cache, and test artifacts 17 | __pycache__/ 18 | **.py[cod] 19 | .pytest_cache/ 20 | .coverage 21 | dist/ 22 | build/ 23 | 24 | # Logs and local environment files 25 | *.log 26 | logs/ 27 | .env 28 | .env.local 29 | 30 | # Documentation and project files 31 | docs/ 32 | README.md 33 | LICENSE 34 | 35 | # Exclude Docker-related files to prevent them from being copied into the image 36 | Dockerfile 37 | .dockerignore 38 | docker-compose.yml 39 | 40 | # OS-specific files 41 | .DS_Store -------------------------------------------------------------------------------- /tracker/backend/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /tracker/backend/.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /tracker/backend/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/tracker/backend/README.md -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/tracker/backend/coreproject_tracker/__init__.py -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/app.py: -------------------------------------------------------------------------------- 1 | from quart import Quart 2 | from quart_cors import cors 3 | from quart_redis import RedisHandler 4 | 5 | try: 6 | from flask_orjson import OrjsonProvider # type: ignore[import] 7 | 8 | HAS_FLASK_ORJSON = True 9 | except ImportError: 10 | HAS_FLASK_ORJSON = False 11 | 12 | from coreproject_tracker.envs import REDIS_DATABASE, REDIS_HOST, REDIS_PORT 13 | from coreproject_tracker.servers import http_blueprint, ws_blueprint 14 | 15 | 16 | def make_app() -> Quart: 17 | app = Quart(__name__) 18 | app = cors(app, allow_origin="*") 19 | 20 | if HAS_FLASK_ORJSON: 21 | app.json = OrjsonProvider(app) # type: ignore 22 | 23 | # Config 24 | app.config["REDIS_URI"] = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DATABASE}" 25 | 26 | # Handler 27 | RedisHandler(app) 28 | 29 | app.register_blueprint(http_blueprint) 30 | app.register_blueprint(ws_blueprint) 31 | 32 | return app 33 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/constants/__init__.py: -------------------------------------------------------------------------------- 1 | from .interval import ( 2 | ANNOUNCE_INTERVAL as ANNOUNCE_INTERVAL, 3 | ) 4 | from .peers import ( 5 | DEFAULT_ANNOUNCE_PEERS as DEFAULT_ANNOUNCE_PEERS, 6 | MAX_ANNOUNCE_PEERS as MAX_ANNOUNCE_PEERS, 7 | ) 8 | from .redis import ( 9 | HASH_EXPIRE_TIME as HASH_EXPIRE_TIME, 10 | REDIS_SERVER_VERSION as REDIS_SERVER_VERSION, 11 | ) 12 | from .ttl import ( 13 | CONNECTION_TTL as CONNECTION_TTL, 14 | PEER_TTL as PEER_TTL, 15 | ) 16 | from .udp import CONNECTION_ID as CONNECTION_ID 17 | from .websocket import ( 18 | WEBSOCKET_INTERVAL as WEBSOCKET_INTERVAL, 19 | WEBSOCKET_PEER_TTL as WEBSOCKET_PEER_TTL, 20 | ) 21 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/constants/interval.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | ANNOUNCE_INTERVAL = int(timedelta(hours=1).total_seconds() / 60) # 1 hour 4 | WEBSOCKET_INTERVAL = int(timedelta(minutes=2).total_seconds()) # 2 min 5 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/constants/peers.py: -------------------------------------------------------------------------------- 1 | DEFAULT_ANNOUNCE_PEERS = 50 2 | MAX_ANNOUNCE_PEERS = 82 3 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/constants/redis.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | HASH_EXPIRE_TIME = int(timedelta(days=1).total_seconds()) 4 | 5 | # Minimum redis version we support 6 | REDIS_SERVER_VERSION = "7.4.2" 7 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/constants/ttl.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | PEER_TTL = int(timedelta(hours=1).total_seconds()) 4 | CONNECTION_TTL = int(timedelta(minutes=5).total_seconds()) 5 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/constants/udp.py: -------------------------------------------------------------------------------- 1 | CONNECTION_ID = (0x417 << 32) | 0x27101980 2 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/constants/websocket.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | WEBSOCKET_PEER_TTL = int(timedelta(minutes=1).total_seconds()) 4 | WEBSOCKET_INTERVAL = WEBSOCKET_PEER_TTL / 2 5 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/converters/__init__.py: -------------------------------------------------------------------------------- 1 | from .bytes import convert_binary_string_to_bytes as convert_binary_string_to_bytes 2 | from .ip import convert_ip as convert_ip 3 | from .numbers import convert_str_int_to_float as convert_str_int_to_float 4 | from .url import convert_to_url_bytes as convert_to_url_bytes 5 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/converters/bytes.py: -------------------------------------------------------------------------------- 1 | __all__ = ["convert_binary_string_to_bytes"] 2 | 3 | 4 | def convert_binary_string_to_bytes(value: str | None) -> bytes | None: 5 | if value: 6 | return value.encode("latin1") 7 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/converters/ip.py: -------------------------------------------------------------------------------- 1 | from coreproject_tracker.functions import convert_ipv4_coded_ipv6_to_ipv4 2 | 3 | __all__ = ["convert_ip"] 4 | 5 | 6 | def convert_ip(value: str) -> str: 7 | ipv4_address = convert_ipv4_coded_ipv6_to_ipv4(value) 8 | if ipv4_address is False: 9 | raise ValueError(f"Invalid IPv4 address: {value}") 10 | 11 | if isinstance(ipv4_address, str): 12 | return ipv4_address 13 | else: 14 | return value 15 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/converters/numbers.py: -------------------------------------------------------------------------------- 1 | _all_ = ["convert_str_int_to_float"] 2 | 3 | 4 | def convert_str_int_to_float(num: str | int | None) -> float | None: 5 | if num: 6 | return float(num) 7 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/converters/url.py: -------------------------------------------------------------------------------- 1 | import urllib.parse 2 | 3 | __all__ = ["convert_to_url_bytes"] 4 | 5 | 6 | def convert_to_url_bytes(value: str) -> bytes: 7 | return urllib.parse.unquote_to_bytes(value) 8 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/datastructures/__init__.py: -------------------------------------------------------------------------------- 1 | # Immutable data structures 2 | from .immutable import ( 3 | HttpDatastructure as HttpDatastructure, 4 | RedisDatastructure as RedisDatastructure, 5 | UdpDatastructure as UdpDatastructure, 6 | WebsocketDatastructure as WebsocketDatastructure, 7 | ) 8 | 9 | # Mutable data structures 10 | from .mutable import MutableBox as MutableBox 11 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/datastructures/immutable/__init__.py: -------------------------------------------------------------------------------- 1 | from .http import HttpDatastructure as HttpDatastructure 2 | from .redis import RedisDatastructure as RedisDatastructure 3 | from .udp import UdpDatastructure as UdpDatastructure 4 | from .websocket import WebsocketDatastructure as WebsocketDatastructure 5 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/datastructures/immutable/http.py: -------------------------------------------------------------------------------- 1 | from attrs import define, field, validators 2 | 3 | from coreproject_tracker.constants import DEFAULT_ANNOUNCE_PEERS, MAX_ANNOUNCE_PEERS 4 | from coreproject_tracker.converters import ( 5 | convert_ip, 6 | convert_to_url_bytes, 7 | ) 8 | from coreproject_tracker.enums import EVENT_NAMES 9 | from coreproject_tracker.validators import ( 10 | validate_20_length, 11 | validate_ip, 12 | validate_port, 13 | ) 14 | 15 | __all__ = ["HttpDatastructure"] 16 | 17 | 18 | @define 19 | class HttpDatastructure: 20 | info_hash_raw: bytes = field( 21 | converter=convert_to_url_bytes, 22 | validator=[validate_20_length], 23 | ) 24 | port: int = field(converter=int, validator=[validate_port]) 25 | left: int = field(converter=int) 26 | numwant: int = field(converter=int) 27 | peer_id: str = field(validator=validators.instance_of(str)) 28 | peer_ip: str = field(converter=convert_ip, validator=[validate_ip]) 29 | 30 | event_name: EVENT_NAMES = field(default=None) 31 | 32 | # Derived 33 | info_hash: str = field(init=False) 34 | 35 | def __attrs_post_init__(self) -> None: 36 | self.numwant = min(self.numwant or DEFAULT_ANNOUNCE_PEERS, MAX_ANNOUNCE_PEERS) 37 | 38 | # Derived Data 39 | self.info_hash = self.info_hash_raw.hex() 40 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/datastructures/mutable/__init__.py: -------------------------------------------------------------------------------- 1 | from .box import MutableBox as MutableBox 2 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/datastructures/mutable/box.py: -------------------------------------------------------------------------------- 1 | from typing import Generic, TypeVar 2 | 3 | from attrs import define 4 | 5 | T = TypeVar("T") 6 | 7 | 8 | @define 9 | class MutableBox(Generic[T]): 10 | """A simple mutable container for any type T.""" 11 | 12 | value: T 13 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/enums/__init__.py: -------------------------------------------------------------------------------- 1 | from .actions import ACTIONS as ACTIONS 2 | from .enum import EVENT_NAMES as EVENT_NAMES 3 | from .ip import IP as IP 4 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/enums/actions.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | __all__ = ["ACTIONS"] 4 | 5 | 6 | class ACTIONS(enum.IntEnum): 7 | CONNECT = 0 8 | ANNOUNCE = 1 9 | SCRAPE = 2 10 | ERROR = 3 11 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/enums/enum.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | __all__ = ["EVENT_NAMES"] 4 | 5 | 6 | class EVENT_NAMES(Enum): 7 | UPDATE = auto() 8 | COMPLETE = auto() 9 | START = auto() 10 | STOP = auto() 11 | PAUSE = auto() 12 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/enums/ip.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | __all__ = ["IP"] 4 | 5 | 6 | class IP(Enum): 7 | IPV4 = auto() 8 | IPV6 = auto() 9 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/envs/__init__.py: -------------------------------------------------------------------------------- 1 | from .redis import ( 2 | REDIS_DATABASE as REDIS_DATABASE, 3 | REDIS_HOST as REDIS_HOST, 4 | REDIS_PORT as REDIS_PORT, 5 | ) 6 | from .workers import WORKERS_COUNT as WORKERS_COUNT 7 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/envs/redis.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | __all__ = ["REDIS_HOST", "REDIS_PORT", "REDIS_DATABASE"] 4 | 5 | REDIS_HOST = os.environ.get("REDIS_HOST", "localhost") 6 | REDIS_PORT = os.environ.get("REDIS_PORT", 6379) 7 | REDIS_DATABASE = os.environ.get("REDIS_DATABASE", 0) 8 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/envs/workers.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | import os 3 | 4 | __all__ = ["WORKERS_COUNT"] 5 | 6 | WORKERS_COUNT = int( 7 | os.environ.get( 8 | "WORKERS_COUNT", 9 | max( 10 | 2, # 2 is here cause of `http and websocket server` and `UDP server` each should use 1 core 11 | multiprocessing.cpu_count() 12 | - 2, # 2 is here cause of `redis` and `UDP server` each needs 1 core 13 | ), 14 | ) 15 | ) 16 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/functions/__init__.py: -------------------------------------------------------------------------------- 1 | from .array import get_n_random_items as get_n_random_items 2 | from .bytes import ( 3 | from_uint16 as from_uint16, 4 | from_uint32 as from_uint32, 5 | from_uint64 as from_uint64, 6 | to_uint32 as to_uint32, 7 | ) 8 | from .convertion import ( 9 | bytes_to_bin_str as bytes_to_bin_str, 10 | hex_str_to_bin_str as hex_str_to_bin_str, 11 | ) 12 | from .dictionary import decode_dictionary as decode_dictionary 13 | from .events import ( 14 | convert_event_id_to_event_enum as convert_event_id_to_event_enum, 15 | convert_event_name_to_event_enum as convert_event_name_to_event_enum, 16 | ) 17 | from .ip import ( 18 | addr_to_ip_port as addr_to_ip_port, 19 | addrs_to_compact as addrs_to_compact, 20 | check_ip_type as check_ip_type, 21 | convert_ipv4_coded_ipv6_to_ipv4 as convert_ipv4_coded_ipv6_to_ipv4, 22 | convert_str_to_ip_object as convert_str_to_ip_object, 23 | ) 24 | from .redis import ( 25 | get_all_hash_keys as get_all_hash_keys, 26 | hdel as hdel, 27 | hget as hget, 28 | hset as hset, 29 | ) 30 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/functions/array.py: -------------------------------------------------------------------------------- 1 | import random 2 | from typing import Iterable 3 | 4 | __all__ = ["get_n_random_items"] 5 | 6 | 7 | async def get_n_random_items[T](array: Iterable[T], n: int) -> list[T]: 8 | """Given a set of array elements, returns n random elements from array""" 9 | # Convert to list if it's not already one 10 | if not isinstance(array, list): 11 | array = list(array) 12 | ret: list[T] = [] 13 | 14 | try: 15 | ret = random.sample(array, n) 16 | except ValueError: 17 | ret = array 18 | finally: 19 | return ret 20 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/functions/bytes.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | __all__ = ["to_uint32", "from_uint16", "from_uint32", "from_uint64"] 4 | 5 | 6 | async def to_uint32(value: int) -> bytes: 7 | """Convert an integer to a 4-byte unsigned integer in network byte order.""" 8 | return struct.pack(">I", value) 9 | 10 | 11 | async def from_uint16(data: bytes) -> int: 12 | """Convert an 2-byte `unsigned short` buffer into an integer.""" 13 | return struct.unpack(">H", data)[0] 14 | 15 | 16 | async def from_uint32(data: bytes) -> int: 17 | """Convert an 2-byte `unsigned int` buffer into an integer.""" 18 | return struct.unpack(">I", data)[0] 19 | 20 | 21 | def from_uint64(data: bytes) -> int: 22 | """Convert an 8-byte `unsigned long long` buffer into an integer.""" 23 | return struct.unpack(">Q", data)[0] 24 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/functions/convertion.py: -------------------------------------------------------------------------------- 1 | async def bytes_to_bin_str(byte: bytes) -> str: 2 | return byte.decode("latin-1") 3 | 4 | 5 | async def hex_str_to_bin_str(hex_str: str) -> str: 6 | return bytes.fromhex(hex_str).decode("latin1") 7 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/functions/dictionary.py: -------------------------------------------------------------------------------- 1 | async def decode_dictionary(data): 2 | if isinstance(data, dict): 3 | return {k.decode(): await decode_dictionary(v) for k, v in data.items()} 4 | elif isinstance(data, list): 5 | return [await decode_dictionary(i) for i in data] 6 | elif isinstance(data, bytes): 7 | return data.decode() 8 | else: 9 | return data 10 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/functions/events.py: -------------------------------------------------------------------------------- 1 | from coreproject_tracker.enums import EVENT_NAMES 2 | 3 | 4 | async def convert_event_id_to_event_enum(event_id: int) -> EVENT_NAMES: 5 | match event_id: 6 | case 0: 7 | return EVENT_NAMES.UPDATE 8 | case 1: 9 | return EVENT_NAMES.COMPLETE 10 | case 2: 11 | return EVENT_NAMES.START 12 | case 3: 13 | return EVENT_NAMES.STOP 14 | case 4: 15 | return EVENT_NAMES.PAUSE 16 | case _: 17 | raise ValueError("`event_id` is not supported") 18 | 19 | 20 | async def convert_event_name_to_event_enum(event_name: str | None) -> EVENT_NAMES: 21 | if event_name: 22 | event_name = event_name.lower() 23 | 24 | match event_name: 25 | case "update": 26 | return EVENT_NAMES.UPDATE 27 | case "completed": 28 | return EVENT_NAMES.COMPLETE 29 | case "started": 30 | return EVENT_NAMES.START 31 | case "stopped": 32 | return EVENT_NAMES.STOP 33 | case "paused": 34 | return EVENT_NAMES.PAUSE 35 | case None: 36 | raise ValueError("`event_name` is None") 37 | case _: 38 | raise ValueError("`event_name` not supported") 39 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/servers/__init__.py: -------------------------------------------------------------------------------- 1 | from .http import http_blueprint as http_blueprint 2 | from .udp import run_udp_server as run_udp_server 3 | from .websocket import ws_blueprint as ws_blueprint 4 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/transaction/__init__.py: -------------------------------------------------------------------------------- 1 | from .rollback import rollback_on_exception as rollback_on_exception 2 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/transaction/rollback.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from contextlib import contextmanager 3 | from typing import TYPE_CHECKING, Any, Iterator 4 | 5 | if TYPE_CHECKING: 6 | from coreproject_tracker.datastructures import MutableBox 7 | 8 | __all__ = ["rollback_on_exception"] 9 | 10 | 11 | @contextmanager 12 | def rollback_on_exception(*boxes: "MutableBox[Any]") -> Iterator[None]: 13 | """ 14 | Context manager that restores Box values if an exception is raised inside the block. 15 | """ 16 | # Take a deep copy snapshot of each box's value 17 | old_values = [copy.deepcopy(b.value) for b in boxes] 18 | try: 19 | yield 20 | except Exception: 21 | # Roll back each box to its original value 22 | for b, old in zip(boxes, old_values): 23 | b.value = old 24 | raise 25 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/validators/__init__.py: -------------------------------------------------------------------------------- 1 | from .connection import validate_connection_id as validate_connection_id 2 | from .ip import validate_ip as validate_ip 3 | from .length import validate_20_length as validate_20_length 4 | from .peer import validate_peer_length as validate_peer_length 5 | from .port import validate_port as validate_port 6 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/validators/connection.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from attrs import Attribute 4 | 5 | from coreproject_tracker.constants import CONNECTION_ID 6 | from coreproject_tracker.functions import from_uint64 7 | 8 | __all__ = ["validate_connection_id"] 9 | 10 | 11 | def validate_connection_id(inst: Any, attr: Attribute, value: Optional[bytes]) -> None: 12 | if value: 13 | connection_id_unpacked = from_uint64(value) 14 | if connection_id_unpacked != CONNECTION_ID: 15 | raise ValueError( 16 | f"`{attr}` of `{inst}` is {value} which not {CONNECTION_ID}" 17 | ) 18 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/validators/ip.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from attrs import Attribute 4 | 5 | from coreproject_tracker.functions import convert_str_to_ip_object 6 | 7 | 8 | def validate_ip(inst: Any, attr: Attribute, value: Optional[str]) -> None: 9 | if value and not convert_str_to_ip_object(value): 10 | raise ValueError(f"`{value}` is not a valid ip.") 11 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/validators/length.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from attrs import Attribute 4 | 5 | __all__ = ["validate_20_length"] 6 | 7 | 8 | def validate_20_length(inst: Any, attr: Attribute, value: Optional[bytes]) -> None: 9 | if length := value: 10 | if len(length) != 20: 11 | raise ValueError(f"`{attr}` of `{inst}` is {value} which not 20 bytes") 12 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/validators/peer.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from attrs import Attribute 4 | 5 | 6 | def validate_peer_length(inst: Any, attr: Attribute, value: Optional[str]) -> None: 7 | if value and len(value) != 20: 8 | raise ValueError(f"`{attr}` of `{inst}` is {value} which not 20 bytes") 9 | -------------------------------------------------------------------------------- /tracker/backend/coreproject_tracker/validators/port.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from attrs import Attribute 4 | 5 | __all__ = ["validate_port"] 6 | 7 | 8 | def validate_port(inst: Any, attr: Attribute, value: Optional[int]) -> None: 9 | if value: 10 | if value <= 0 and value >= 65535: 11 | raise ValueError( 12 | f"`{attr}` of `{inst}` is {value} which is not in range(0, 65535)" 13 | ) 14 | -------------------------------------------------------------------------------- /tracker/backend/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "tracker" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.13" 7 | dependencies = [ 8 | "anyio>=4.8.0", 9 | "attrs>=25.1.0", 10 | "bencode-py>=4.0.0", 11 | "flask-orjson>=2.0.0; platform.python_implementation == 'CPython'", 12 | "hypercorn>=0.17.3", 13 | "quart>=0.20.0", 14 | "quart-cors>=0.8.0", 15 | "quart-redis>=3.0.0", 16 | "redis[hiredis]>=5.2.1", 17 | "uvloop>=0.21.0; sys_platform == 'linux'", 18 | ] 19 | 20 | [tool.setuptools] 21 | packages = ["coreproject_tracker"] 22 | 23 | [project.scripts] 24 | coreproject_tracker = "coreproject_tracker.cli:main" 25 | 26 | [dependency-groups] 27 | dev = [ 28 | "py-spy>=0.4.0", 29 | ] 30 | lint = [ 31 | "ruff>=0.9.7", 32 | 33 | ] 34 | 35 | [tool.uv] 36 | default-groups = ["dev", "lint"] 37 | package = true 38 | 39 | [tool.ruff.lint] 40 | extend-select = ["I"] # Enables isort rules 41 | 42 | [tool.ruff.format] 43 | skip-magic-trailing-comma = false # Avoids conflict with isort.split-on-trailing-comma 44 | 45 | [tool.ruff.lint.isort] 46 | split-on-trailing-comma = true # Keep this aligned with format.skip-magic-trailing-comma 47 | combine-as-imports = true 48 | -------------------------------------------------------------------------------- /tracker/backend/tests/udp_client.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | # Define server address and port 4 | SERVER_ADDRESS = "127.0.0.1" # Change to the server's IP if needed 5 | SERVER_PORT = 5000 # Change to the appropriate port 6 | 7 | # Create a UDP socket 8 | client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 9 | 10 | try: 11 | while True: 12 | message = input("Enter message to send (or 'exit' to quit): ") 13 | if message.lower() == "exit": 14 | break 15 | 16 | # Send message to server 17 | client_socket.sendto(message.encode(), (SERVER_ADDRESS, SERVER_PORT)) 18 | 19 | # Receive response from server 20 | data, server = client_socket.recvfrom(1024) 21 | print(f"Received from server: {data.decode()}") 22 | 23 | finally: 24 | client_socket.close() 25 | print("Connection closed.") 26 | -------------------------------------------------------------------------------- /tracker/backend/tests/websocket_client.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import websockets 4 | 5 | 6 | async def websocket_client(): 7 | uri = "ws://localhost:5000" # Change this to your WebSocket server URL 8 | 9 | while True: 10 | async with websockets.connect(uri) as websocket: 11 | ask = input() 12 | await websocket.send(ask) 13 | print(f"Sent: {ask}") 14 | 15 | response = await websocket.recv() 16 | print(f"Received: {response}") 17 | 18 | 19 | if __name__ == "__main__": 20 | asyncio.run(websocket_client()) 21 | -------------------------------------------------------------------------------- /tracker/deploy.sh: -------------------------------------------------------------------------------- 1 | docker compose down && docker compose build --no-cache && docker compose up 2 | -------------------------------------------------------------------------------- /tracker/frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .dockerignore 3 | node_modules 4 | npm-debug.log 5 | README.md 6 | .next 7 | .git -------------------------------------------------------------------------------- /tracker/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /tracker/frontend/.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true -------------------------------------------------------------------------------- /tracker/frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-tailwindcss", "prettier-plugin-packagejson"] 3 | } 4 | -------------------------------------------------------------------------------- /tracker/frontend/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs", 3 | "options": { 4 | "allow-unauthenticated": true, 5 | "memory": "256Mi", 6 | "cpu": "1", 7 | "port": 3000, 8 | "http2": false 9 | } 10 | } -------------------------------------------------------------------------------- /tracker/frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /tracker/frontend/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /tracker/frontend/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | // images: { unoptimized: true }, 5 | output: "standalone", 6 | productionBrowserSourceMaps: true, 7 | experimental: { 8 | reactCompiler: true, 9 | }, 10 | 11 | turbopack: { 12 | rules: { 13 | "*.svg": { 14 | as: "*.ts", 15 | loaders: ["@svgr/webpack"], 16 | }, 17 | }, 18 | }, 19 | webpack(config) { 20 | config.module.rules.push({ 21 | test: /\.svg$/i, 22 | use: ["@svgr/webpack"], 23 | }); 24 | return config; 25 | }, 26 | }; 27 | 28 | export default nextConfig; 29 | -------------------------------------------------------------------------------- /tracker/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "next build", 7 | "dev": "cross-env NODE_OPTIONS='--inspect' next dev --turbopack", 8 | "lint": "next lint", 9 | "start": "next start" 10 | }, 11 | "browserslist": [ 12 | "IE 11", 13 | "last 2 versions", 14 | "> 0.2%", 15 | "not dead" 16 | ], 17 | "dependencies": { 18 | "@radix-ui/react-dropdown-menu": "^2.1.15", 19 | "@radix-ui/react-slot": "^1.2.3", 20 | "class-variance-authority": "^0.7.1", 21 | "clsx": "^2.1.1", 22 | "next": "15.3.3", 23 | "react": "^19.1.0", 24 | "react-dom": "^19.1.0" 25 | }, 26 | "devDependencies": { 27 | "@eslint/eslintrc": "^3", 28 | "@svgr/webpack": "^8.1.0", 29 | "@tailwindcss/postcss": "^4", 30 | "@types/bencode": "^2.0.4", 31 | "@types/node": "^22", 32 | "@types/react": "^19", 33 | "@types/react-dom": "^19", 34 | "@types/webtorrent": "^0.110.0", 35 | "babel-plugin-react-compiler": "^19.1.0-rc.2", 36 | "bencode": "^4.0.0", 37 | "cross-env": "^7.0.3", 38 | "eslint": "^9", 39 | "eslint-config-next": "15.3.3", 40 | "lucide-react": "^0.511.0", 41 | "next-themes": "^0.4.6", 42 | "prettier": "^3.5.3", 43 | "prettier-plugin-packagejson": "^2.5.15", 44 | "prettier-plugin-tailwindcss": "^0.6.12", 45 | "swr": "^2.3.3", 46 | "tailwind-merge": "^3.3.0", 47 | "tailwindcss": "^4", 48 | "tw-animate-css": "^1.3.0", 49 | "typescript": "^5" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tracker/frontend/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /tracker/frontend/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/tracker/frontend/src/app/favicon.ico -------------------------------------------------------------------------------- /tracker/frontend/src/components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 5 | 6 | export function ThemeProvider({ 7 | children, 8 | ...props 9 | }: React.ComponentProps) { 10 | return {children}; 11 | } 12 | -------------------------------------------------------------------------------- /tracker/frontend/src/constants/url.ts: -------------------------------------------------------------------------------- 1 | export const BACKEND_HOST = 2 | process.env.NEXT_PUBLIC_BACKEND_HOST || "localhost:5000"; 3 | 4 | export const HTTP_ENDPOINT = `http://${BACKEND_HOST}`; 5 | export const WS_ENDPOINT = `ws://${BACKEND_HOST}/`; 6 | 7 | // Websocket 8 | export const WS_TRACKER_ENDPOINT = `${WS_ENDPOINT}/announce`; 9 | 10 | // HTTP 11 | export const HTTP_ANNOUNCE_ENDPOINT = `${HTTP_ENDPOINT}/announce`; 12 | export const API_URL = `${HTTP_ENDPOINT}/api`; 13 | export const HTTP_TRACKER_ENDPOINT = `${HTTP_ENDPOINT}/http`; 14 | -------------------------------------------------------------------------------- /tracker/frontend/src/fonts/Kokoro/Kokoro-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/tracker/frontend/src/fonts/Kokoro/Kokoro-Bold.woff2 -------------------------------------------------------------------------------- /tracker/frontend/src/fonts/Kokoro/Kokoro-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/tracker/frontend/src/fonts/Kokoro/Kokoro-BoldItalic.woff2 -------------------------------------------------------------------------------- /tracker/frontend/src/fonts/Kokoro/Kokoro-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/tracker/frontend/src/fonts/Kokoro/Kokoro-Italic.woff2 -------------------------------------------------------------------------------- /tracker/frontend/src/fonts/Kokoro/Kokoro-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/tracker/frontend/src/fonts/Kokoro/Kokoro-Regular.woff2 -------------------------------------------------------------------------------- /tracker/frontend/src/fonts/Kokoro/Kokoro-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/tracker/frontend/src/fonts/Kokoro/Kokoro-SemiBold.woff2 -------------------------------------------------------------------------------- /tracker/frontend/src/fonts/Kokoro/Kokoro-SemiBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreproject-moe/CoreProject/5db520028dd9d7505e5865516349e4fd8bf87ff4/tracker/frontend/src/fonts/Kokoro/Kokoro-SemiBoldItalic.woff2 -------------------------------------------------------------------------------- /tracker/frontend/src/functions/bencode.ts: -------------------------------------------------------------------------------- 1 | import bencode from "bencode"; 2 | 3 | export function isDataBencoded(data: string) { 4 | try { 5 | bencode.decode(Buffer.from(data)); 6 | return true; 7 | } catch { 8 | return false; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tracker/frontend/src/hooks/useBackendData.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import useSWR from "swr"; 3 | import { API_URL } from "@/constants/url"; 4 | import { BackendData } from "@/types/api"; 5 | 6 | const fetcher = async (url: URL) => { 7 | const res = await fetch(url); 8 | 9 | const data = await res.json(); 10 | 11 | return data; 12 | }; 13 | 14 | export function useBackendData() { 15 | const { data, error, isLoading } = useSWR(API_URL, fetcher); 16 | 17 | return { 18 | data: data ?? null, 19 | isLoading, 20 | isError: error, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /tracker/frontend/src/hooks/useHttpData.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import useSWR from "swr"; 4 | import { HTTP_ANNOUNCE_ENDPOINT } from "@/constants/url"; 5 | import { URL } from "node:url"; 6 | // Fake url: http://localhost:5000/?info_hash=%234筗%E1%83{r%DD^%1a%0a%F2%03%D0%D9l%DBj&peer_id=-qB5040-6CdLzJpk(Zrn&port=52629&uploaded=0&downloaded=0&left=0&corrupt=0&key=A7E1171F&event=started&numwant=200&compact=1&no_peer_id=1&supportcrypto=1&redundant=0 7 | const fetcher = async (url: URL) => { 8 | const res = await fetch(url); 9 | 10 | const text = await res.text(); 11 | 12 | return { 13 | status: res.status, 14 | body: text, 15 | }; 16 | }; 17 | 18 | export function useHttpData() { 19 | const URL = `${HTTP_ANNOUNCE_ENDPOINT}?info_hash=%234筗%E1%83{r%DD^%1a%0a%F2%03%D0%D9l%DBj&peer_id=-qB5040-6CdLzJpk(Zrn&port=52629&uploaded=0&downloaded=0&left=0&corrupt=0&key=A7E1171F&event=started&numwant=200&compact=1&no_peer_id=1&supportcrypto=1&redundant=0`; 20 | const { data, error, isLoading } = useSWR(URL, fetcher); 21 | 22 | return { 23 | data: data?.body, 24 | status: data?.status, 25 | isLoading: isLoading, 26 | isError: error, 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /tracker/frontend/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /tracker/frontend/src/types/api.ts: -------------------------------------------------------------------------------- 1 | export interface BackendData { 2 | quart_version: string; 3 | redis_version: { 4 | client: string; 5 | server: string; 6 | }; 7 | python_version: string; 8 | redis_data: { 9 | [infoHash: string]: { 10 | [peerAddress: string]: string; 11 | }; 12 | }; 13 | } 14 | 15 | export interface RedisData { 16 | info_hash: string; 17 | type: "http" | "udp"; 18 | peer_id: string; 19 | peer_ip: string; 20 | port: number; 21 | left: number | null; 22 | } 23 | -------------------------------------------------------------------------------- /tracker/frontend/src/types/iframe.ts: -------------------------------------------------------------------------------- 1 | export type IframeMessage = { 2 | from: string; 3 | message: string; 4 | }; 5 | -------------------------------------------------------------------------------- /tracker/frontend/svgr.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg" { 2 | import { FC, SVGProps } from "react"; 3 | const content: FC>; 4 | export default content; 5 | } 6 | 7 | declare module "*.svg?url" { 8 | const content: any; 9 | export default content; 10 | } 11 | -------------------------------------------------------------------------------- /tracker/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": [ 26 | "svgr.d.ts", 27 | "next-env.d.ts", 28 | "**/*.ts", 29 | "**/*.tsx", 30 | ".next/types/**/*.ts", 31 | "src/app/test/page.tsx", 32 | "src/app/torrent/page.jsx" 33 | ], 34 | "exclude": ["node_modules"] 35 | } 36 | -------------------------------------------------------------------------------- /tracker/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.23-alpine 2 | 3 | # Copy configuration 4 | COPY nginx.conf /etc/nginx/nginx.conf 5 | 6 | # Expose ports 7 | EXPOSE 80 8 | EXPOSE 5000/udp 9 | 10 | # Health check (optional) 11 | HEALTHCHECK --interval=30s --timeout=3s \ 12 | CMD wget --quiet --tries=1 --spider http://localhost/health || exit 1 13 | 14 | # Start NGINX 15 | CMD ["nginx", "-g", "daemon off;"] --------------------------------------------------------------------------------