├── discord
├── __init__.py
├── models
│ └── .gitkeep
├── web
│ ├── views
│ │ ├── .gitkeep
│ │ └── login.py
│ ├── template
│ │ └── .gitkeep
│ └── main.py
├── alembic
│ ├── README
│ └── script.py.mako
├── TODO.md
├── settings.py
├── pyproject.toml
└── database.py
├── tracker
├── backend
│ ├── README.md
│ ├── .python-version
│ ├── coreproject_tracker
│ │ ├── __init__.py
│ │ ├── constants
│ │ │ ├── udp.py
│ │ │ ├── peers.py
│ │ │ ├── ttl.py
│ │ │ ├── websocket.py
│ │ │ ├── redis.py
│ │ │ ├── interval.py
│ │ │ └── __init__.py
│ │ ├── exceptions
│ │ │ ├── redis.py
│ │ │ └── __init__.py
│ │ ├── datastructures
│ │ │ ├── mutable
│ │ │ │ ├── __init__.py
│ │ │ │ └── box.py
│ │ │ ├── immutable
│ │ │ │ ├── __init__.py
│ │ │ │ ├── http.py
│ │ │ │ ├── redis.py
│ │ │ │ └── udp.py
│ │ │ └── __init__.py
│ │ ├── transaction
│ │ │ ├── __init__.py
│ │ │ └── rollback.py
│ │ ├── singletons
│ │ │ └── __init__.py
│ │ ├── enums
│ │ │ ├── ip.py
│ │ │ ├── redis.py
│ │ │ ├── actions.py
│ │ │ ├── __init__.py
│ │ │ └── enum.py
│ │ ├── servers
│ │ │ └── __init__.py
│ │ ├── converters
│ │ │ ├── numbers.py
│ │ │ ├── url.py
│ │ │ ├── bytes.py
│ │ │ ├── __init__.py
│ │ │ └── ip.py
│ │ ├── functions
│ │ │ ├── convertion.py
│ │ │ ├── dictionary.py
│ │ │ ├── array.py
│ │ │ ├── bytes.py
│ │ │ ├── __init__.py
│ │ │ └── events.py
│ │ ├── envs
│ │ │ ├── __init__.py
│ │ │ ├── redis.py
│ │ │ └── workers.py
│ │ ├── validators
│ │ │ ├── peer.py
│ │ │ ├── __init__.py
│ │ │ ├── ip.py
│ │ │ ├── length.py
│ │ │ ├── port.py
│ │ │ └── connection.py
│ │ └── app.py
│ ├── .gitattributes
│ ├── tests_primitive
│ │ ├── websocket_client.py
│ │ └── udp_client.py
│ ├── .dockerignore
│ ├── pyproject.toml
│ └── Dockerfile
├── frontend
│ ├── .npmrc
│ ├── .prettierrc
│ ├── src
│ │ ├── types
│ │ │ ├── iframe.ts
│ │ │ └── api.ts
│ │ ├── app
│ │ │ ├── favicon.ico
│ │ │ └── layout.tsx
│ │ ├── fonts
│ │ │ └── Kokoro
│ │ │ │ ├── Kokoro-Bold.woff2
│ │ │ │ ├── Kokoro-Italic.woff2
│ │ │ │ ├── Kokoro-Regular.woff2
│ │ │ │ ├── Kokoro-SemiBold.woff2
│ │ │ │ ├── Kokoro-BoldItalic.woff2
│ │ │ │ └── Kokoro-SemiBoldItalic.woff2
│ │ ├── lib
│ │ │ └── utils.ts
│ │ ├── functions
│ │ │ └── bencode.ts
│ │ ├── components
│ │ │ └── theme-provider.tsx
│ │ ├── constants
│ │ │ └── url.ts
│ │ └── hooks
│ │ │ ├── useBackendData.ts
│ │ │ └── useHttpData.ts
│ ├── .dockerignore
│ ├── postcss.config.mjs
│ ├── app.json
│ ├── svgr.d.ts
│ ├── eslint.config.mjs
│ ├── components.json
│ ├── next.config.ts
│ ├── .gitignore
│ ├── tsconfig.json
│ ├── package.json
│ └── README.md
├── deploy.sh
├── nginx
│ ├── Dockerfile
│ └── nginx.conf
└── docker-compose.yml
├── backend
├── README.md
├── django_core
│ ├── apps
│ │ ├── anime
│ │ │ ├── __init__.py
│ │ │ ├── migrations
│ │ │ │ ├── __init__.py
│ │ │ │ ├── 0006_merge_20230326_1819.py
│ │ │ │ ├── 0002_alter_animemodel_rating.py
│ │ │ │ ├── 0008_rename_updated_animemodel_updated_at.py
│ │ │ │ ├── 0009_animegenremodel_description.py
│ │ │ │ ├── 0010_animemodel_is_locked.py
│ │ │ │ ├── 0013_animethememodel_description.py
│ │ │ │ ├── 0020_alter_animegenremodel_type.py
│ │ │ │ ├── 0005_alter_animeendingmodel_unique_together_and_more.py
│ │ │ │ ├── 0025_alter_animemodel_comments_delete_animecommentmodel.py
│ │ │ │ ├── 0019_animemodel_anime_name_name_japanese_idx.py
│ │ │ │ ├── 0002_animemodel_staffs_alter_animemodel_rating.py
│ │ │ │ ├── 0007_animemodel_created_at_alter_animemodel_updated.py
│ │ │ │ ├── 0018_alter_animenamesynonymmodel_name_and_more.py
│ │ │ │ ├── 0014_alter_animegenremodel_is_locked_and_more.py
│ │ │ │ ├── 0015_animemodel_anime_anime_name_301f1e_gin_and_more.py
│ │ │ │ ├── 0026_alter_animegenremodel_is_locked_and_more.py
│ │ │ │ ├── 0023_alter_animegenremodel_is_locked_and_more.py
│ │ │ │ ├── 0011_animegenremodel_created_at_animegenremodel_is_locked_and_more.py
│ │ │ │ ├── 0012_animethememodel_created_at_animethememodel_is_locked_and_more.py
│ │ │ │ ├── 0021_alter_animemodel_banner_background_color_and_more.py
│ │ │ │ ├── 0022_alter_animegenremodel_created_at_and_more.py
│ │ │ │ ├── 0004_alter_animeendingmodel_options_and_more.py
│ │ │ │ ├── 0017_remove_animemodel_anime_name_name_japanese_idx_and_more.py
│ │ │ │ └── 0016_remove_animemodel_anime_anime_name_301f1e_gin_and_more.py
│ │ │ ├── apps.py
│ │ │ ├── admin
│ │ │ │ ├── anime_genre.py
│ │ │ │ ├── anime_theme.py
│ │ │ │ └── anime_openings_and_endings.py
│ │ │ ├── forms.py
│ │ │ ├── models
│ │ │ │ ├── anime_genre.py
│ │ │ │ ├── anime_theme.py
│ │ │ │ └── anime_openings_and_endings.py
│ │ │ └── signals.py
│ │ ├── api
│ │ │ ├── __init__.py
│ │ │ ├── schemas
│ │ │ │ ├── user
│ │ │ │ │ ├── login.py
│ │ │ │ │ ├── username_validity.py
│ │ │ │ │ └── __init__.py
│ │ │ │ ├── stats
│ │ │ │ │ └── histogram.py
│ │ │ │ ├── episodes
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ └── episode_timestamp.py
│ │ │ │ ├── anime
│ │ │ │ │ ├── anime_opening_and_ending.py
│ │ │ │ │ ├── anime_genre.py
│ │ │ │ │ └── anime_theme.py
│ │ │ │ ├── trackers
│ │ │ │ │ ├── mal.py
│ │ │ │ │ ├── kitsu.py
│ │ │ │ │ ├── anilist.py
│ │ │ │ │ └── __init__.py
│ │ │ │ ├── characters
│ │ │ │ │ └── __init__.py
│ │ │ │ ├── producers
│ │ │ │ │ └── __init__.py
│ │ │ │ └── staffs
│ │ │ │ │ └── __init__.py
│ │ │ ├── filters
│ │ │ │ ├── genres.py
│ │ │ │ ├── themes.py
│ │ │ │ ├── openings_and_endings.py
│ │ │ │ ├── studios.py
│ │ │ │ ├── producers.py
│ │ │ │ ├── characters.py
│ │ │ │ ├── staffs.py
│ │ │ │ └── anime.py
│ │ │ ├── admin.py
│ │ │ ├── http.py
│ │ │ ├── permissions.py
│ │ │ ├── views
│ │ │ │ ├── user
│ │ │ │ │ ├── logout.py
│ │ │ │ │ ├── username_validity.py
│ │ │ │ │ ├── signup.py
│ │ │ │ │ ├── login.py
│ │ │ │ │ └── __init__.py
│ │ │ │ └── anime
│ │ │ │ │ ├── anime_staff.py
│ │ │ │ │ ├── anime_producer.py
│ │ │ │ │ ├── anime_studio.py
│ │ │ │ │ ├── anime_genre.py
│ │ │ │ │ ├── anime_theme.py
│ │ │ │ │ ├── anime_endings.py
│ │ │ │ │ ├── anime_openings.py
│ │ │ │ │ ├── genres.py
│ │ │ │ │ ├── themes.py
│ │ │ │ │ ├── endings.py
│ │ │ │ │ ├── openings.py
│ │ │ │ │ └── anime_character.py
│ │ │ ├── parser.py
│ │ │ ├── models.py
│ │ │ ├── auth.py
│ │ │ └── decorator.py
│ │ ├── staffs
│ │ │ ├── __init__.py
│ │ │ ├── migrations
│ │ │ │ ├── __init__.py
│ │ │ │ ├── 0005_alter_staffmodel_is_locked.py
│ │ │ │ ├── 0009_alter_staffmodel_is_locked.py
│ │ │ │ ├── 0008_alter_staffmodel_is_locked.py
│ │ │ │ ├── 0004_staffmodel_is_locked.py
│ │ │ │ ├── 0002_alter_staffalternatenamemodel_options.py
│ │ │ │ ├── 0007_alter_staffmodel_created_at.py
│ │ │ │ ├── 0003_staffmodel_created_at_staffmodel_updated_at.py
│ │ │ │ └── 0006_staffalternatenamemodel_staff_alternate_idx_and_more.py
│ │ │ ├── apps.py
│ │ │ └── admin.py
│ │ ├── user
│ │ │ ├── __init__.py
│ │ │ ├── migrations
│ │ │ │ ├── __init__.py
│ │ │ │ ├── 0009_merge_20240317_1131.py
│ │ │ │ ├── 0003_delete_token.py
│ │ │ │ ├── 0005_remove_customuser_ip.py
│ │ │ │ ├── 0002_alter_token_unique_together.py
│ │ │ │ ├── 0008_remove_customuser_created_at.py
│ │ │ │ ├── 0011_remove_customuser_created_at.py
│ │ │ │ ├── 0006_customuser_created_at.py
│ │ │ │ ├── 0004_alter_customuser_avatar.py
│ │ │ │ ├── 0008_alter_customuser_username.py
│ │ │ │ ├── 0010_customuser_created_at_alter_customuser_username.py
│ │ │ │ └── 0007_alter_customuser_unique_together_and_more.py
│ │ │ ├── tests.py
│ │ │ ├── validators
│ │ │ │ ├── __init__.py
│ │ │ │ └── username.py
│ │ │ ├── apps.py
│ │ │ ├── urls.py
│ │ │ ├── forms.py
│ │ │ ├── backends.py
│ │ │ └── managers.py
│ │ ├── characters
│ │ │ ├── __init__.py
│ │ │ ├── migrations
│ │ │ │ ├── __init__.py
│ │ │ │ ├── 0004_alter_charactermodel_is_locked.py
│ │ │ │ ├── 0007_alter_charactermodel_is_locked.py
│ │ │ │ ├── 0006_alter_charactermodel_is_locked.py
│ │ │ │ ├── 0003_charactermodel_is_locked.py
│ │ │ │ ├── 0005_alter_charactermodel_created_at.py
│ │ │ │ ├── 0002_charactermodel_created_at_charactermodel_updated_at.py
│ │ │ │ └── 0001_initial.py
│ │ │ ├── apps.py
│ │ │ ├── admin.py
│ │ │ └── models.py
│ │ ├── comments
│ │ │ ├── __init__.py
│ │ │ ├── migrations
│ │ │ │ ├── __init__.py
│ │ │ │ ├── 0002_alter_commentmodel_options.py
│ │ │ │ ├── 0006_commentmodel_deleted.py
│ │ │ │ ├── 0007_alter_commentmodel_user.py
│ │ │ │ ├── 0003_commentmodel_dislikes_commentmodel_likes.py
│ │ │ │ ├── 0004_alter_commentmodel_dislikes_alter_commentmodel_likes.py
│ │ │ │ └── 0005_remove_commentmodel_dislikes_and_more.py
│ │ │ ├── tests.py
│ │ │ ├── views.py
│ │ │ ├── apps.py
│ │ │ ├── admin.py
│ │ │ └── models.py
│ │ ├── episodes
│ │ │ ├── __init__.py
│ │ │ ├── migrations
│ │ │ │ ├── __init__.py
│ │ │ │ ├── 0007_remove_episodetimestampmodel_episode.py
│ │ │ │ ├── 0002_episodemodel_episode_type.py
│ │ │ │ ├── 0008_alter_episodecommentmodel_options.py
│ │ │ │ ├── 0010_alter_episodemodel_episode_comments_and_more.py
│ │ │ │ ├── 0004_episodemodel_created_at_episodemodel_updated_at.py
│ │ │ │ ├── 0005_remove_episodecommentmodel_comment_added_and_more.py
│ │ │ │ └── 0009_alter_episodecommentmodel_created_at_and_more.py
│ │ │ ├── admin.py
│ │ │ ├── apps.py
│ │ │ ├── forms.py
│ │ │ ├── admin
│ │ │ │ ├── __init__.py
│ │ │ │ └── episode_timestamp.py
│ │ │ └── models
│ │ │ │ └── episode_timestamp.py
│ │ └── producers
│ │ │ ├── __init__.py
│ │ │ ├── migrations
│ │ │ ├── __init__.py
│ │ │ ├── 0003_remove_producermodel_default_title.py
│ │ │ ├── 0006_alter_producermodel_is_locked.py
│ │ │ ├── 0004_rename_japanese_title_producermodel_name_japanese.py
│ │ │ ├── 0009_alter_producermodel_is_locked.py
│ │ │ ├── 0008_alter_producermodel_is_locked.py
│ │ │ ├── 0005_producermodel_is_locked.py
│ │ │ ├── 0007_alter_producermodel_created_at.py
│ │ │ ├── 0002_producermodel_created_at_producermodel_updated_at.py
│ │ │ └── 0001_initial.py
│ │ │ ├── apps.py
│ │ │ ├── admin.py
│ │ │ └── models.py
│ ├── static_src
│ │ └── .gitkeep
│ ├── media
│ │ ├── episode
│ │ │ └── .gitignore
│ │ ├── anime
│ │ │ └── .gitignore
│ │ ├── avatars
│ │ │ └── .gitignore
│ │ ├── banner
│ │ │ └── .gitignore
│ │ ├── cover
│ │ │ └── .gitignore
│ │ ├── ending
│ │ │ └── .gitignore
│ │ ├── opening
│ │ │ └── .gitignore
│ │ ├── staffs
│ │ │ └── .gitignore
│ │ ├── characters
│ │ │ └── .gitignore
│ │ └── episode_cover
│ │ │ └── .gitignore
│ ├── core
│ │ ├── __init__.py
│ │ ├── asgi.py
│ │ ├── storages.py
│ │ └── celery.py
│ ├── templates
│ │ ├── anime
│ │ │ ├── episode
│ │ │ │ └── index.html
│ │ │ ├── explore
│ │ │ │ └── index.html
│ │ │ ├── index.html
│ │ │ └── info
│ │ │ │ └── index.html
│ │ ├── admin
│ │ │ └── base.html
│ │ ├── upload
│ │ │ └── index.html
│ │ ├── home
│ │ │ └── index.html
│ │ ├── user
│ │ │ ├── login
│ │ │ │ └── index.html
│ │ │ ├── register
│ │ │ │ └── index.html
│ │ │ ├── reset_password
│ │ │ │ └── index.html
│ │ │ ├── _layout.html
│ │ │ └── user_does_not_exist.php
│ │ └── errors
│ │ │ └── base.html
│ ├── mixins
│ │ └── models
│ │ │ ├── is_locked.py
│ │ │ ├── updated_at.py
│ │ │ └── created_at.py
│ ├── utilities
│ │ ├── rgb_to_hex.py
│ │ └── format.py
│ └── manage.py
├── .gitattributes
└── .env.example
├── CREDITS.md
├── .github
├── workflows
│ └── tracker.yml
└── dependabot.yml
└── FAQ.md
/discord/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord/web/views/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord/web/views/login.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tracker/backend/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord/web/template/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/README.md:
--------------------------------------------------------------------------------
1 | # Django-Template
2 |
--------------------------------------------------------------------------------
/backend/django_core/apps/anime/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/django_core/apps/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/django_core/apps/staffs/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/django_core/apps/user/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/django_core/static_src/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tracker/backend/.python-version:
--------------------------------------------------------------------------------
1 | 3.13
2 |
--------------------------------------------------------------------------------
/backend/django_core/apps/characters/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/django_core/apps/comments/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/django_core/apps/episodes/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/django_core/apps/producers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tracker/backend/coreproject_tracker/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tracker/frontend/.npmrc:
--------------------------------------------------------------------------------
1 | legacy-peer-deps=true
--------------------------------------------------------------------------------
/backend/django_core/apps/anime/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/django_core/apps/user/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/django_core/apps/characters/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/django_core/apps/comments/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/django_core/apps/episodes/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/django_core/apps/producers/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/django_core/apps/staffs/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/django_core/apps/user/tests.py:
--------------------------------------------------------------------------------
1 | # Create your tests here.
2 |
--------------------------------------------------------------------------------
/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/media/episode/.gitignore:
--------------------------------------------------------------------------------
1 | *.mkv
2 | *.mp4
3 | *.ts
4 |
--------------------------------------------------------------------------------
/backend/django_core/apps/episodes/admin.py:
--------------------------------------------------------------------------------
1 | # Register your models here.
2 |
--------------------------------------------------------------------------------
/discord/alembic/README:
--------------------------------------------------------------------------------
1 | Generic single-database configuration with an async dbapi.
2 |
--------------------------------------------------------------------------------
/CREDITS.md:
--------------------------------------------------------------------------------
1 | # Backend Testing
2 |
3 | [HoriDesu](https://github.com/Praveensenpai)
4 |
--------------------------------------------------------------------------------
/backend/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/tracker/deploy.sh:
--------------------------------------------------------------------------------
1 | docker compose down && docker compose build --no-cache && docker compose up
2 |
--------------------------------------------------------------------------------
/tracker/backend/coreproject_tracker/constants/udp.py:
--------------------------------------------------------------------------------
1 | CONNECTION_ID = (0x417 << 32) | 0x27101980
2 |
--------------------------------------------------------------------------------
/tracker/backend/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/backend/django_core/core/__init__.py:
--------------------------------------------------------------------------------
1 | from .celery import app as celery_app
2 |
3 | __all__ = ("celery_app",)
4 |
--------------------------------------------------------------------------------
/tracker/backend/coreproject_tracker/exceptions/redis.py:
--------------------------------------------------------------------------------
1 | class RedisNotInitialized(Exception):
2 | pass
3 |
--------------------------------------------------------------------------------
/tracker/backend/coreproject_tracker/constants/peers.py:
--------------------------------------------------------------------------------
1 | DEFAULT_ANNOUNCE_PEERS = 50
2 | MAX_ANNOUNCE_PEERS = 82
3 |
--------------------------------------------------------------------------------
/tracker/backend/coreproject_tracker/datastructures/mutable/__init__.py:
--------------------------------------------------------------------------------
1 | from .box import MutableBox as MutableBox
2 |
--------------------------------------------------------------------------------
/backend/django_core/apps/user/validators/__init__.py:
--------------------------------------------------------------------------------
1 | from .username import username_validator as username_validator
2 |
--------------------------------------------------------------------------------
/backend/django_core/templates/anime/episode/index.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tracker/frontend/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["prettier-plugin-tailwindcss", "prettier-plugin-packagejson"]
3 | }
4 |
--------------------------------------------------------------------------------
/tracker/frontend/src/types/iframe.ts:
--------------------------------------------------------------------------------
1 | export type IframeMessage = {
2 | from: string;
3 | message: string;
4 | };
5 |
--------------------------------------------------------------------------------
/tracker/backend/coreproject_tracker/exceptions/__init__.py:
--------------------------------------------------------------------------------
1 | from .redis import RedisNotInitialized as RedisNotInitialized
2 |
--------------------------------------------------------------------------------
/tracker/backend/coreproject_tracker/transaction/__init__.py:
--------------------------------------------------------------------------------
1 | from .rollback import rollback_on_exception as rollback_on_exception
2 |
--------------------------------------------------------------------------------
/tracker/frontend/.dockerignore:
--------------------------------------------------------------------------------
1 | Dockerfile
2 | .dockerignore
3 | node_modules
4 | npm-debug.log
5 | README.md
6 | .next
7 | .git
--------------------------------------------------------------------------------
/tracker/backend/coreproject_tracker/singletons/__init__.py:
--------------------------------------------------------------------------------
1 | from .redis import RedisHandler as RedisHandler, get_redis as get_redis
2 |
--------------------------------------------------------------------------------
/tracker/frontend/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coreproject-moe/coreproject/HEAD/tracker/frontend/src/app/favicon.ico
--------------------------------------------------------------------------------
/backend/django_core/apps/api/schemas/user/login.py:
--------------------------------------------------------------------------------
1 | from ninja import Schema
2 |
3 |
4 | class LoginSchema(Schema):
5 | token: str
6 |
--------------------------------------------------------------------------------
/tracker/frontend/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | const config = {
2 | plugins: ["@tailwindcss/postcss"],
3 | };
4 |
5 | export default config;
6 |
--------------------------------------------------------------------------------
/backend/.env.example:
--------------------------------------------------------------------------------
1 | # POSTGRESQL SETTINGS
2 | POSTGRES_NAME =
3 | POSTGRES_USER =
4 | POSTGRES_PASSWORD =
5 | POSTGRES_HOST =
6 | POSTGRES_PORT =
7 |
--------------------------------------------------------------------------------
/tracker/frontend/src/fonts/Kokoro/Kokoro-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coreproject-moe/coreproject/HEAD/tracker/frontend/src/fonts/Kokoro/Kokoro-Bold.woff2
--------------------------------------------------------------------------------
/tracker/frontend/src/fonts/Kokoro/Kokoro-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coreproject-moe/coreproject/HEAD/tracker/frontend/src/fonts/Kokoro/Kokoro-Italic.woff2
--------------------------------------------------------------------------------
/tracker/frontend/src/fonts/Kokoro/Kokoro-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coreproject-moe/coreproject/HEAD/tracker/frontend/src/fonts/Kokoro/Kokoro-Regular.woff2
--------------------------------------------------------------------------------
/tracker/frontend/src/fonts/Kokoro/Kokoro-SemiBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coreproject-moe/coreproject/HEAD/tracker/frontend/src/fonts/Kokoro/Kokoro-SemiBold.woff2
--------------------------------------------------------------------------------
/tracker/frontend/src/fonts/Kokoro/Kokoro-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coreproject-moe/coreproject/HEAD/tracker/frontend/src/fonts/Kokoro/Kokoro-BoldItalic.woff2
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tracker/frontend/src/fonts/Kokoro/Kokoro-SemiBoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coreproject-moe/coreproject/HEAD/tracker/frontend/src/fonts/Kokoro/Kokoro-SemiBoldItalic.woff2
--------------------------------------------------------------------------------
/tracker/backend/coreproject_tracker/enums/redis.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 |
4 | class REDIS_NAMESPACE_ENUM(str, Enum):
5 | HTTP_UDP = "http_udp"
6 | WEBSOCKET = "websocket"
7 |
--------------------------------------------------------------------------------
/backend/django_core/templates/anime/explore/index.html:
--------------------------------------------------------------------------------
1 | {% block head %}
2 |
Explore - AnimeCore
3 | {% endblock %}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/backend/django_core/templates/admin/base.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/base.html" %}
2 | {% load static %}
3 | {% block extrahead %}
4 |
5 | {% endblock %}
6 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | from .redis import REDIS_NAMESPACE_ENUM as REDIS_NAMESPACE_ENUM
5 |
--------------------------------------------------------------------------------
/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/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/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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%}
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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%}
--------------------------------------------------------------------------------
/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 | REDIS_URI as REDIS_URI,
6 | )
7 | from .workers import WORKERS_COUNT as WORKERS_COUNT
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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_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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/templates/anime/index.html:
--------------------------------------------------------------------------------
1 | {% load static %}
2 |
3 | {% block head %}
4 | AnimeCore
5 | {% endblock %}
6 |
7 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
9 | REDIS_URI = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DATABASE}"
10 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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;"]
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/templates/anime/info/index.html:
--------------------------------------------------------------------------------
1 |
11 |
12 |
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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/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/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_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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/tracker/backend/tests_primitive/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 | reactCompiler:true,
8 | turbopack: {
9 | rules: {
10 | "*.svg": {
11 | as: "*.ts",
12 | loaders: ["@svgr/webpack"],
13 | },
14 | },
15 | },
16 | webpack(config) {
17 | config.module.rules.push({
18 | test: /\.svg$/i,
19 | use: ["@svgr/webpack"],
20 | });
21 | return config;
22 | },
23 | };
24 |
25 | export default nextConfig;
26 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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_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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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_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/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 |
--------------------------------------------------------------------------------
/tracker/backend/.dockerignore:
--------------------------------------------------------------------------------
1 | **/*.egg-info/
2 | tests_primitive/
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
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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_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/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/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/tracker/backend/tests_primitive/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/templates/user/user_does_not_exist.php:
--------------------------------------------------------------------------------
1 |
12 |
13 | User not found {{ user_id }}
14 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/tracker/backend/coreproject_tracker/app.py:
--------------------------------------------------------------------------------
1 | from quart import Quart
2 | from quart_cors import cors
3 |
4 | try:
5 | from flask_orjson import OrjsonProvider # type: ignore[import]
6 |
7 | HAS_FLASK_ORJSON = True
8 | except ImportError:
9 | HAS_FLASK_ORJSON = False
10 |
11 | from coreproject_tracker.envs import REDIS_URI
12 | from coreproject_tracker.servers import http_blueprint, ws_blueprint
13 |
14 |
15 | def make_app() -> Quart:
16 | app = Quart(__name__)
17 | app = cors(app, allow_origin="*")
18 |
19 | if HAS_FLASK_ORJSON:
20 | app.json = OrjsonProvider(app) # type: ignore
21 |
22 | from coreproject_tracker.singletons.redis import RedisHandler
23 |
24 | redis_manager = RedisHandler(REDIS_URI)
25 |
26 | @app.before_serving
27 | async def before_serving():
28 | await redis_manager.init_redis()
29 |
30 | @app.after_serving
31 | async def after_serving():
32 | await redis_manager.close_redis()
33 |
34 | app.register_blueprint(http_blueprint)
35 | app.register_blueprint(ws_blueprint)
36 |
37 | return app
38 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/.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@v6
25 |
26 | - name: Setup Node.js
27 | uses: actions/setup-node@v6
28 | with:
29 | cache: 'npm'
30 | cache-dependency-path: tracker/frontend/package-lock.json
31 |
32 | - name: Install frontend dependencies
33 | run: npm i
34 | working-directory: tracker/frontend
35 |
36 | - name: Build frontend
37 | run: npm run build
38 | working-directory: tracker/frontend
39 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 | "redis[hiredis]>=5.2.1",
16 | "uvloop>=0.21.0; sys_platform == 'linux'",
17 | ]
18 |
19 | [tool.setuptools]
20 | packages = ["coreproject_tracker"]
21 |
22 | [project.scripts]
23 | coreproject_tracker = "coreproject_tracker.__main__:main"
24 |
25 | [dependency-groups]
26 | dev = [
27 | "py-spy>=0.4.0",
28 | ]
29 | lint = [
30 | "ruff>=0.9.7",
31 |
32 | ]
33 |
34 | [tool.uv]
35 | default-groups = ["dev", "lint"]
36 | package = true
37 |
38 | [tool.ruff.lint]
39 | extend-select = ["I"] # Enables isort rules
40 |
41 | [tool.ruff.format]
42 | skip-magic-trailing-comma = false # Avoids conflict with isort.split-on-trailing-comma
43 |
44 | [tool.ruff.lint.isort]
45 | split-on-trailing-comma = true # Keep this aligned with format.skip-magic-trailing-comma
46 | combine-as-imports = true
47 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/.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/github/administering-a-repository/configuration-options-for-dependency-updates
5 | enable-beta-ecosystems: true
6 | version: 2
7 | updates:
8 | # Backend
9 | # =======
10 | - package-ecosystem: pip
11 | directory: /backend/
12 | schedule:
13 | interval: weekly
14 | open-pull-requests-limit: 100
15 |
16 | # Tracker
17 | # =======
18 | - package-ecosystem: uv
19 | directory: /tracker/backend/
20 | schedule:
21 | interval: weekly
22 | open-pull-requests-limit: 100
23 |
24 | - package-ecosystem: npm
25 | directory: /tracker/frontend/
26 | schedule:
27 | interval: weekly
28 | open-pull-requests-limit: 100
29 |
30 | # Bots
31 | # =====
32 | - package-ecosystem: pip
33 | directory: /discord/
34 | schedule:
35 | interval: weekly
36 | open-pull-requests-limit: 100
37 |
38 | #Frontend
39 | #- package-ecosystem: 'npm'
40 | # directory: '/frontend'
41 | # schedule:
42 | # interval: 'weekly'
43 |
44 | # Actions
45 | - package-ecosystem: 'github-actions'
46 | directory: '/'
47 | schedule:
48 | interval: 'weekly'
49 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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.16",
19 | "@radix-ui/react-slot": "^1.2.4",
20 | "class-variance-authority": "^0.7.1",
21 | "clsx": "^2.1.1",
22 | "next": "16.0.10",
23 | "react": "^19.2.1",
24 | "react-dom": "^19.2.1"
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": "^24",
32 | "@types/react": "^19",
33 | "@types/react-dom": "^19",
34 | "@types/webtorrent": "^0.110.1",
35 | "babel-plugin-react-compiler": "^19.1.0-rc.3",
36 | "bencode": "^4.0.0",
37 | "cross-env": "^10.1.0",
38 | "eslint": "^9",
39 | "eslint-config-next": "16.0.8",
40 | "lucide-react": "^0.556.0",
41 | "next-themes": "^0.4.6",
42 | "prettier": "^3.7.4",
43 | "prettier-plugin-packagejson": "^2.5.20",
44 | "prettier-plugin-tailwindcss": "^0.7.2",
45 | "swr": "^2.3.7",
46 | "tailwind-merge": "^3.4.0",
47 | "tailwindcss": "^4",
48 | "tw-animate-css": "^1.4.0",
49 | "typescript": "^5"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tracker/nginx/nginx.conf:
--------------------------------------------------------------------------------
1 | events {
2 | worker_connections 10240;
3 | }
4 |
5 | http {
6 | upstream backend_pool {
7 | server backend:5000;
8 | server backend:5001;
9 | server backend:5002;
10 | server backend:5003;
11 | }
12 |
13 |
14 | server {
15 | listen 80;
16 | server_name _;
17 |
18 | # Combined API & WebSocket endpoints
19 | location ~ ^/(api|announce)(/.*)?$ {
20 | proxy_pass http://backend_pool;
21 | proxy_http_version 1.1; # Insecure HTTP/2
22 |
23 | # Forward headers
24 | proxy_set_header X-Real-IP $remote_addr;
25 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
26 | proxy_set_header X-Forwarded-Proto $scheme;
27 |
28 | # Upgrade websockets
29 | proxy_set_header Upgrade $http_upgrade;
30 | proxy_set_header Connection "Upgrade";
31 | }
32 |
33 | # Combined frontend routes (health check + general routing)
34 | location / {
35 | proxy_pass http://frontend:3000;
36 | proxy_set_header Host $host;
37 | proxy_set_header X-Real-IP $remote_addr;
38 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
39 | proxy_set_header X-Forwarded-Proto $scheme;
40 | }
41 | }
42 | }
43 |
44 | stream {
45 | server {
46 | listen 5000 udp;
47 | proxy_pass backend:5000;
48 | proxy_timeout 1s;
49 | proxy_responses 1;
50 | proxy_bind $remote_addr transparent;
51 | }
52 | }
--------------------------------------------------------------------------------
/tracker/frontend/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
37 |
--------------------------------------------------------------------------------
/backend/django_core/apps/api/views/anime/anime_character.py:
--------------------------------------------------------------------------------
1 | from apps.anime.models import AnimeModel
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.characters.models import CharacterModel
6 | from apps.api.http import HttpRequest
7 | from django.shortcuts import get_list_or_404, get_object_or_404
8 | from ninja import Router
9 |
10 | from ...schemas.characters import CharacterGETSchema
11 |
12 | router = Router()
13 |
14 |
15 | @router.get("/{int:anime_id}/character", response=list[CharacterGETSchema])
16 | def get_individual_anime_character_info(
17 | request: HttpRequest,
18 | anime_id: int,
19 | ) -> list[CharacterModel]:
20 | query = get_list_or_404(
21 | get_object_or_404(AnimeModel, pk=anime_id).characters,
22 | )
23 | return query
24 |
25 |
26 | @router.post(
27 | "/{int:anime_id}/character",
28 | response=list[CharacterGETSchema],
29 | auth=AuthBearer(),
30 | )
31 | @permission_required([IsSuperUser])
32 | def post_individual_anime_character_info(
33 | request: HttpRequest,
34 | anime_id: int,
35 | payload: CharacterGETSchema,
36 | ) -> CharacterModel:
37 | # Set this at top
38 | # Because if there is no anime_info_model with corresponding query
39 | # theres no point in continuing
40 | anime_info_model = get_object_or_404(AnimeModel, pk=anime_id)
41 |
42 | query = CharacterModel.objects.get_or_create(
43 | **payload.dict(),
44 | )
45 | instance: CharacterModel = query[0]
46 | anime_info_model.characters.add(instance)
47 |
48 | return instance
49 |
--------------------------------------------------------------------------------
/backend/django_core/apps/producers/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.7 on 2023-03-08 10:21
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 | initial = True
8 |
9 | dependencies = []
10 |
11 | operations = [
12 | migrations.CreateModel(
13 | name="ProducerModel",
14 | fields=[
15 | (
16 | "id",
17 | models.BigAutoField(
18 | auto_created=True,
19 | primary_key=True,
20 | serialize=False,
21 | verbose_name="ID",
22 | ),
23 | ),
24 | ("mal_id", models.IntegerField(blank=True, null=True, unique=True)),
25 | ("kitsu_id", models.IntegerField(blank=True, null=True, unique=True)),
26 | ("name", models.CharField(default="", max_length=128)),
27 | (
28 | "default_title",
29 | models.CharField(blank=True, default="", max_length=128, null=True),
30 | ),
31 | (
32 | "japanese_title",
33 | models.CharField(blank=True, default="", max_length=128, null=True),
34 | ),
35 | ("established", models.DateTimeField(blank=True, null=True)),
36 | ("about", models.TextField(blank=True, null=True)),
37 | ],
38 | options={
39 | "verbose_name": "Producer",
40 | "verbose_name_plural": "Producers",
41 | },
42 | ),
43 | ]
44 |
--------------------------------------------------------------------------------
/tracker/backend/Dockerfile:
--------------------------------------------------------------------------------
1 | # First, build the application in the `/app` directory.
2 | # See `Dockerfile` for details.
3 | FROM ghcr.io/astral-sh/uv:python3.13-alpine AS builder
4 | ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
5 |
6 | # Disable Python downloads, because we want to use the system interpreter
7 | # across both images. If using a managed Python version, it needs to be
8 | # copied from the build image into the final image; see `standalone.Dockerfile`
9 | # for an example.
10 | ENV UV_PYTHON_DOWNLOADS=0
11 |
12 | WORKDIR /app
13 | RUN --mount=type=cache,target=/root/.cache/uv \
14 | --mount=type=bind,source=uv.lock,target=uv.lock \
15 | --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
16 | uv sync --frozen --no-install-project --no-default-groups
17 | COPY . /app
18 | RUN --mount=type=cache,target=/root/.cache/uv \
19 | uv sync --frozen --no-default-groups
20 |
21 | # Remove the .egg-info file that are created from setuptools
22 | RUN find /app -type d -name '*.egg-info' -exec rm -rf {} +
23 |
24 | # Then, use a final image without uv
25 | FROM python:3.13-alpine
26 | # It is important to use the image that matches the builder, as the path to the
27 | # Python executable must be the same, e.g., using `python:3.11-slim-bookworm`
28 | # will fail.
29 |
30 | # Copy the application from the builder
31 | COPY --from=builder --chown=app:app /app /app
32 |
33 | # Place executables in the environment at the front of the path
34 | ENV PATH="/app/.venv/bin:$PATH"
35 |
36 | ENV HOST=127.0.0.1
37 | ENV PORT=5000
38 | EXPOSE 5000/udp 5000/tcp
39 |
40 | # Run the FastAPI application by default
41 | CMD ["sh", "-c", "coreproject_tracker --host ${HOST} --port ${PORT}"]
42 |
--------------------------------------------------------------------------------
/tracker/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | redis:
3 | image: redis:7.4.3-alpine
4 | container_name: redis
5 | restart: unless-stopped
6 | ports:
7 | - "16379:16379"
8 | volumes:
9 | - redis_data:/data
10 | command: redis-server --port 16379
11 | healthcheck:
12 | test: [ "CMD", "redis-cli", "-p", "16379", "ping" ]
13 | interval: 10s
14 | timeout: 5s
15 | retries: 5
16 |
17 | backend:
18 | build:
19 | context: ./backend
20 | dockerfile: Dockerfile
21 | container_name: backend
22 | restart: unless-stopped
23 | depends_on:
24 | redis:
25 | condition: service_healthy
26 | environment:
27 | REDIS_HOST: redis
28 | REDIS_PORT: 16379
29 | HOST: "0.0.0.0"
30 | PORT: 5000
31 | WORKERS_COUNT: 4
32 | ports:
33 | - "5000:5000"
34 |
35 | frontend:
36 | build:
37 | context: ./frontend
38 | dockerfile: Dockerfile
39 | container_name: frontend
40 | restart: unless-stopped
41 | environment:
42 | NEXT_PUBLIC_BACKEND_URL: http://localhost:5000
43 | HOST: "0.0.0.0"
44 | PORT: 3000
45 | NODE_ENV: production
46 | ports:
47 | - "3000:3000"
48 | depends_on:
49 | - backend
50 |
51 | nginx:
52 | # build instead of pull
53 | build:
54 | context: ./nginx
55 | dockerfile: Dockerfile
56 |
57 | container_name: nginx
58 | restart: unless-stopped
59 |
60 | ports:
61 | - "80:80"
62 | - "5000:5000/udp"
63 |
64 | depends_on:
65 | - backend
66 | - frontend
67 |
68 | volumes:
69 | redis_data:
70 |
--------------------------------------------------------------------------------
/backend/django_core/apps/user/migrations/0007_alter_customuser_unique_together_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.3 on 2023-07-21 13:13
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", "0006_customuser_created_at"),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterUniqueTogether(
15 | name="customuser",
16 | unique_together=set(),
17 | ),
18 | migrations.AlterField(
19 | model_name="customuser",
20 | name="username",
21 | field=models.CharField(
22 | error_messages={"unique": "A user with that username already exists."},
23 | help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
24 | max_length=150,
25 | unique=True,
26 | validators=[
27 | apps.user.validators.username.username_validator,
28 | django.core.validators.RegexValidator(
29 | "^[a-zA-Z0-9_-]+#[0-9]{4}$",
30 | message="Username is not valid for this regex `^[a-zA-Z0-9_-]+#[0-9]{4}$`",
31 | ),
32 | ],
33 | verbose_name="username",
34 | ),
35 | ),
36 | migrations.AlterUniqueTogether(
37 | name="customuser",
38 | unique_together={("username", "email")},
39 | ),
40 | migrations.RemoveField(
41 | model_name="customuser",
42 | name="discriminator",
43 | ),
44 | ]
45 |
--------------------------------------------------------------------------------
/backend/django_core/apps/user/backends.py:
--------------------------------------------------------------------------------
1 | import contextlib
2 | from typing import Any, cast
3 |
4 | from apps.user.models import CustomUser
5 | from django.contrib.auth.backends import ModelBackend
6 | from django.contrib.auth.models import AbstractBaseUser
7 | from django.db.models import Q
8 | from apps.api.http import HttpRequest
9 |
10 |
11 | class EmailOrUsernameModelBackend(ModelBackend):
12 | """
13 | Authentication backend which allows users to authenticate using either their
14 | username or email address
15 |
16 | Source: https://stackoverflow.com/a/35836674/59984
17 | """
18 |
19 | @staticmethod
20 | def get_user_given_username_and_password(
21 | username_or_email: str | None,
22 | password: str | None,
23 | ) -> CustomUser | None:
24 | if not username_or_email and not password:
25 | return None
26 | query = None
27 |
28 | # Supress user doesn't exist
29 | with contextlib.suppress(CustomUser.DoesNotExist):
30 | user_model = CustomUser.objects.get(
31 | Q(username=username_or_email) | Q(email__iexact=username_or_email)
32 | )
33 |
34 | # If password matches then return user
35 | if user_model.check_password(password) and password:
36 | query = user_model
37 |
38 | return query
39 |
40 | def authenticate(
41 | self,
42 | request: HttpRequest | None,
43 | username: str | None = None,
44 | password: str | None = None,
45 | **kwargs: Any,
46 | ) -> AbstractBaseUser | None:
47 | user_model = self.get_user_given_username_and_password(username, password)
48 | return cast(AbstractBaseUser, user_model)
49 |
--------------------------------------------------------------------------------
/backend/django_core/core/celery.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from celery import Celery
4 | from celery.schedules import crontab
5 |
6 | # Set the default Django settings module for the 'celery' program.
7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
8 |
9 | app = Celery("core")
10 |
11 | ## Get the base REDIS URL, default to redis' default
12 | BASE_REDIS_URL = os.environ.get("REDIS_URL", "redis://localhost:6379")
13 |
14 | # Using a string here means the worker doesn't have to serialize
15 | # the configuration object to child processes.
16 | # - namespace='CELERY' means all celery-related configuration keys
17 | # should have a `CELERY_` prefix.
18 | app.config_from_object("django.conf:settings", namespace="CELERY")
19 |
20 | # Load task modules from all registered Django apps.
21 | app.autodiscover_tasks()
22 |
23 | # Celery beat
24 | app.conf.beat_schedule = {
25 | # Characters
26 | # ==========
27 | # Executes every Friday night at 12:00 a.m.
28 | "get-periodic-characters-every-friday-morning": {
29 | "task": "apps.characters.tasks.get_periodic_character",
30 | "schedule": crontab(hour=0, minute=00, day_of_week=5),
31 | },
32 | # Staffs / People
33 | # ==========
34 | # Executes every Friday night at 12:00 a.m.
35 | "get-periodic-staffs-every-friday-morning": {
36 | "task": "apps.staffs.tasks.get_periodic_staff",
37 | "schedule": crontab(hour=0, minute=00, day_of_week=5),
38 | },
39 | # Anime
40 | # ======
41 | # Genre
42 | "get-periodic-genres-every-friday-night": {
43 | "task": "apps.anime.tasks.get_periodic_anime_genres",
44 | "schedule": crontab(hour=0, minute=00, day_of_week=5),
45 | },
46 | }
47 |
48 | app.conf.timezone = "UTC"
49 |
50 | app.conf.broker_url = BASE_REDIS_URL
51 |
--------------------------------------------------------------------------------
/backend/django_core/apps/user/managers.py:
--------------------------------------------------------------------------------
1 | from typing import TYPE_CHECKING, Any
2 |
3 | from django.contrib.auth.base_user import BaseUserManager
4 | from django.utils.translation import gettext_lazy as _
5 |
6 | if TYPE_CHECKING:
7 | from .models import CustomUser
8 |
9 |
10 | class UserManager(
11 | BaseUserManager["CustomUser"],
12 | ):
13 | """
14 | Custom user model manager where email is the unique identifiers
15 | for authentication instead of usernames.
16 | """
17 |
18 | def create_user(
19 | self,
20 | email: str,
21 | password: str,
22 | **extra_fields: dict[str, dict[str, Any]],
23 | ) -> "CustomUser":
24 | """Create and save a User with the given email and password."""
25 |
26 | if not email:
27 | raise ValueError(_("The Email must be set"))
28 | email = self.normalize_email(email)
29 | user: CustomUser = self.model(email=email, **extra_fields)
30 | user.set_password(password)
31 | user.save()
32 | return user
33 |
34 | def create_superuser(
35 | self,
36 | email: str,
37 | password: str,
38 | **extra_fields: Any,
39 | ) -> "CustomUser":
40 | """Create and save a SuperUser with the given email and password."""
41 |
42 | extra_fields.setdefault("is_staff", True)
43 | extra_fields.setdefault("is_superuser", True)
44 | extra_fields.setdefault("is_active", True)
45 |
46 | # Sanity Check
47 | if extra_fields.get("is_staff") is not True:
48 | raise ValueError(_("Superuser must have is_staff=True."))
49 | if extra_fields.get("is_superuser") is not True:
50 | raise ValueError(_("Superuser must have is_superuser=True."))
51 |
52 | return self.create_user(email, password, **extra_fields)
53 |
--------------------------------------------------------------------------------
/backend/django_core/apps/anime/models/anime_openings_and_endings.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from dynamic_filenames import FilePattern
3 |
4 | opening_upload_pattern = FilePattern(filename_pattern="opening/{uuid:s}{ext}")
5 | ending_upload_pattern = FilePattern(filename_pattern="ending/{uuid:s}{ext}")
6 |
7 |
8 | # Create your models here.
9 |
10 |
11 | class AbstractBaseOpeningAndEndingModel(models.Model):
12 | # Either opening number/closing number
13 | entry = models.BigIntegerField(null=False, blank=False)
14 | # Opening/closing theme name
15 | name = models.CharField(max_length=512, blank=False, null=False)
16 | # Canonical URL
17 | url = models.URLField(blank=False, null=True)
18 |
19 | class Meta:
20 | abstract = True
21 |
22 |
23 | class AnimeOpeningModel(AbstractBaseOpeningAndEndingModel):
24 | thumbnail = models.ImageField(
25 | upload_to=opening_upload_pattern,
26 | default=None,
27 | blank=True,
28 | null=True,
29 | )
30 |
31 | def __str__(self) -> str:
32 | return f"{self.entry}. {self.name}"
33 |
34 | class Meta:
35 | unique_together = [
36 | ("entry", "name", "url"),
37 | ]
38 | verbose_name = "Anime Opening"
39 | verbose_name_plural = "Anime Openings"
40 |
41 |
42 | class AnimeEndingModel(AbstractBaseOpeningAndEndingModel):
43 | thumbnail = models.ImageField(
44 | upload_to=ending_upload_pattern,
45 | default=None,
46 | blank=True,
47 | null=True,
48 | )
49 |
50 | def __str__(self) -> str:
51 | return f"{self.entry}. {self.name}"
52 |
53 | class Meta:
54 | unique_together = [
55 | ("entry", "name", "url"),
56 | ]
57 | verbose_name = "Anime Ending"
58 | verbose_name_plural = "Anime Endings"
59 |
--------------------------------------------------------------------------------
/backend/django_core/apps/characters/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.7 on 2023-03-08 10:21
2 |
3 | import dynamic_filenames
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 | initial = True
9 |
10 | dependencies = []
11 |
12 | operations = [
13 | migrations.CreateModel(
14 | name="CharacterModel",
15 | fields=[
16 | (
17 | "id",
18 | models.BigAutoField(
19 | auto_created=True,
20 | primary_key=True,
21 | serialize=False,
22 | verbose_name="ID",
23 | ),
24 | ),
25 | ("mal_id", models.IntegerField(null=True, unique=True)),
26 | ("kitsu_id", models.IntegerField(null=True, unique=True)),
27 | ("anilist_id", models.IntegerField(null=True, unique=True)),
28 | ("name", models.CharField(max_length=1024)),
29 | ("name_kanji", models.CharField(blank=True, max_length=1024, null=True)),
30 | (
31 | "character_image",
32 | models.ImageField(
33 | blank=True,
34 | default=None,
35 | null=True,
36 | upload_to=dynamic_filenames.FilePattern(
37 | filename_pattern="characters/{uuid:s}{ext}"
38 | ),
39 | ),
40 | ),
41 | ("about", models.TextField(blank=True, null=True)),
42 | ],
43 | options={
44 | "verbose_name": "Character",
45 | "verbose_name_plural": "Characters",
46 | },
47 | ),
48 | ]
49 |
--------------------------------------------------------------------------------
/tracker/backend/coreproject_tracker/datastructures/immutable/redis.py:
--------------------------------------------------------------------------------
1 | from attrs import asdict, define, field, validators
2 | from quart import json
3 |
4 | from coreproject_tracker.constants import PEER_TTL, WEBSOCKET_PEER_TTL
5 | from coreproject_tracker.converters import (
6 | convert_str_int_to_float,
7 | )
8 | from coreproject_tracker.enums import REDIS_NAMESPACE_ENUM
9 | from coreproject_tracker.functions import (
10 | hset,
11 | )
12 | from coreproject_tracker.validators import (
13 | validate_ip,
14 | validate_port,
15 | )
16 |
17 |
18 | @define
19 | class RedisDatastructure:
20 | info_hash: str = field(
21 | validator=validators.instance_of(str), metadata={"asdict": False}
22 | )
23 | type: str = field(validator=validators.instance_of(str))
24 | peer_id: str = field(validator=validators.instance_of(str))
25 | peer_ip: str = field(validator=[validate_ip])
26 | port: int = field(converter=int, validator=[validate_port])
27 | left: float | None = field(converter=convert_str_int_to_float)
28 |
29 | async def save(self) -> None:
30 | """
31 | Save the object to Redis.
32 | """
33 |
34 | # CONSTANT
35 | match self.type:
36 | case "websocket":
37 | expire_time = WEBSOCKET_PEER_TTL
38 | redis_namespace = REDIS_NAMESPACE_ENUM.WEBSOCKET
39 | case "http" | "udp":
40 | expire_time = PEER_TTL
41 | redis_namespace = REDIS_NAMESPACE_ENUM.HTTP_UDP
42 | case _:
43 | raise ValueError(f"{self.type} is not a valid type")
44 |
45 | await hset(
46 | self.info_hash,
47 | f"{self.peer_ip}:{self.port}",
48 | json.dumps(asdict(self, recurse=True)),
49 | expire_time=expire_time,
50 | namespace=redis_namespace,
51 | )
52 |
--------------------------------------------------------------------------------
/FAQ.md:
--------------------------------------------------------------------------------
1 |
2 | How do you plan to fund this site?
3 |
4 | => I don't know, for now at least. But I have made the site to be as cost effective as possible.
5 | I plan on funding through patreon at some point.
6 |
7 |
8 |
9 |
10 | Where do you get the contents from?
11 | => Mostly from nyaa
12 |
13 |
14 |
15 | What will you do to a DMCA takedown?
16 |
17 | => I am not storing much for this site. But in reality our site will eventually be DMCA striked.
18 | Thats why this site will have an import export feature. Since the code is
19 | open source , anyone can host the site and the user can just import the data.
21 |
22 |
23 |
24 |
25 | When can we expect a working site ?
26 |
27 | => I don't know actually. With my current financial status, it's really hard for me to host this
28 | site (We need around 25$ - 30$ to host this, hopefully ! ).
29 |
30 |
31 |
32 |
33 | Whats the gurantee that an anime will stay forever?
34 |
35 | => Theres no gurantee. But we have 2 cloud provider, an offline hard disk backup. Unless we get
36 | DMCA striked at both CDN, my house catches on fire, all the content uploaded on the site should
37 | stay for lifetime
38 |
39 |
40 |
41 |
42 | But the x,y,z site does this better.
43 |
44 | => I am sorry I couldn't meet your expectations. So maybe you can make
45 | this
46 | site look even better?
47 |
48 |
49 |
--------------------------------------------------------------------------------
/tracker/frontend/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import "./globals.css";
3 | import { ThemeProvider } from "@/components/theme-provider";
4 | import { Navbar } from "@/components/navbar";
5 | import localFont from "next/font/local";
6 | export const metadata: Metadata = {
7 | title: "Create Next App",
8 | description: "Generated by create next app",
9 | };
10 |
11 | const KokoroFont = localFont({
12 | src: [
13 | // Regular
14 | {
15 | path: "../fonts/Kokoro/Kokoro-Regular.woff2",
16 | weight: "400",
17 | style: "normal",
18 | },
19 | {
20 | path: "../fonts/Kokoro/Kokoro-SemiBold.woff2",
21 | weight: "500",
22 | style: "italic",
23 | },
24 | {
25 | path: "../fonts/Kokoro/Kokoro-Bold.woff2",
26 | weight: "700",
27 | style: "normal",
28 | },
29 |
30 | // Italic
31 | {
32 | path: "../fonts/Kokoro/Kokoro-Italic.woff2",
33 | weight: "400",
34 | style: "italic",
35 | },
36 | {
37 | path: "../fonts/Kokoro/Kokoro-SemiBoldItalic.woff2",
38 | weight: "500",
39 | style: "italic",
40 | },
41 |
42 | {
43 | path: "../fonts/Kokoro/Kokoro-BoldItalic.woff2",
44 | weight: "700",
45 | style: "italic",
46 | },
47 | ],
48 | });
49 |
50 | export default function RootLayout({
51 | children,
52 | }: Readonly<{
53 | children: React.ReactNode;
54 | }>) {
55 | return (
56 |
57 |
58 |
64 |
65 | {children}
66 |
67 |
68 |
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/tracker/backend/coreproject_tracker/datastructures/immutable/udp.py:
--------------------------------------------------------------------------------
1 | from attrs import define, field, validators
2 |
3 | from coreproject_tracker.constants import (
4 | ANNOUNCE_INTERVAL,
5 | DEFAULT_ANNOUNCE_PEERS,
6 | MAX_ANNOUNCE_PEERS,
7 | )
8 | from coreproject_tracker.enums import EVENT_NAMES
9 | from coreproject_tracker.validators import (
10 | validate_20_length,
11 | validate_connection_id,
12 | validate_ip,
13 | validate_port,
14 | )
15 |
16 | __all__ = ["UdpDatastructure"]
17 |
18 |
19 | @define
20 | class UdpDatastructure:
21 | # CONSTANT
22 | interval: int = field(
23 | init=False, default=ANNOUNCE_INTERVAL, validator=validators.instance_of(int)
24 | )
25 |
26 | connection_id: bytes = field(
27 | validator=[validators.instance_of(bytes), validate_connection_id]
28 | )
29 | action: int = field(validator=validators.instance_of(int))
30 | transaction_id: int = field(validator=validators.instance_of(int))
31 |
32 | # Only available on ANNOUNCE
33 | info_hash: bytes = field(default=None, validator=[validate_20_length])
34 | peer_id: str = field(default=None)
35 | downloaded: int = field(default=None)
36 | left: int = field(default=None)
37 | uploaded: int = field(default=None)
38 | key: int = field(default=None)
39 | numwant: int = field(default=DEFAULT_ANNOUNCE_PEERS)
40 |
41 | # Set from function call
42 | peers: bytes = field(default=None) # TODO: make a log_2 based validator here
43 | incomplete: int = field(default=0)
44 | complete: int = field(default=0)
45 |
46 | # Might not need these
47 | ip: str = field(default=None, validator=[validate_ip])
48 | port: int = field(default=None, validator=[validate_port])
49 |
50 | # Derived
51 | event_name: EVENT_NAMES = field(default=None)
52 |
53 | def __attrs_post_init__(self):
54 | self.numwant = min(self.numwant, MAX_ANNOUNCE_PEERS)
55 |
--------------------------------------------------------------------------------