diff --git a/Dockerfile b/Dockerfile index acf66baf..3121bf0d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,6 @@ FROM node:20-alpine AS frontend WORKDIR /app COPY frontend/package*.json ./ -RUN npm install esbuild@0.19.9 RUN npm install COPY frontend/ . RUN npm run build && npm prune --production diff --git a/backend/program/__init__.py b/backend/program/__init__.py index 6629f229..e65ad7bf 100644 --- a/backend/program/__init__.py +++ b/backend/program/__init__.py @@ -1,4 +1,2 @@ """Program main module""" -from program.program import Program, Event - - +from program.program import Program, Event # noqa: F401 \ No newline at end of file diff --git a/backend/program/content/__init__.py b/backend/program/content/__init__.py index e1a53fd3..704878ca 100644 --- a/backend/program/content/__init__.py +++ b/backend/program/content/__init__.py @@ -1,4 +1,4 @@ -from .mdblist import Mdblist -from .overseerr import Overseerr -from .plex_watchlist import PlexWatchlist -from .listrr import Listrr \ No newline at end of file +from .mdblist import Mdblist # noqa: F401 +from .overseerr import Overseerr # noqa: F401 +from .plex_watchlist import PlexWatchlist # noqa: F401 +from .listrr import Listrr # noqa: F401 \ No newline at end of file diff --git a/backend/program/indexers/__init__.py b/backend/program/indexers/__init__.py index a41e522b..e69de29b 100644 --- a/backend/program/indexers/__init__.py +++ b/backend/program/indexers/__init__.py @@ -1 +0,0 @@ -from .trakt import TraktIndexer \ No newline at end of file diff --git a/backend/program/indexers/trakt.py b/backend/program/indexers/trakt.py index 84c8e88a..11d8d2fb 100644 --- a/backend/program/indexers/trakt.py +++ b/backend/program/indexers/trakt.py @@ -1,10 +1,9 @@ """Trakt updater module""" from datetime import datetime, timedelta -from typing import Optional from utils.logger import logger from utils.request import get -from program.media.item import Movie, Show, Season, Episode, MediaItem, ItemId +from program.media.item import Movie, Show, Season, Episode, MediaItem from program.settings.manager import settings_manager CLIENT_ID = "0183a05ad97098d87287fe46da4ae286f434f32e8e951caad4cc147c947d79a3" diff --git a/backend/program/libaries/__init__.py b/backend/program/libaries/__init__.py index 229bed76..aefecc83 100644 --- a/backend/program/libaries/__init__.py +++ b/backend/program/libaries/__init__.py @@ -1,2 +1,2 @@ -from .plex import PlexLibrary -from .symlink import SymlinkLibrary \ No newline at end of file +from .plex import PlexLibrary # noqa: F401 +from .symlink import SymlinkLibrary # noqa: F401 diff --git a/backend/program/libaries/plex.py b/backend/program/libaries/plex.py index facbdb7a..8326fb74 100644 --- a/backend/program/libaries/plex.py +++ b/backend/program/libaries/plex.py @@ -1,10 +1,8 @@ """Plex library module""" - +import os import concurrent.futures from threading import Lock -import os from datetime import datetime -from typing import Optional from plexapi.server import PlexServer from plexapi.exceptions import BadRequest, Unauthorized from utils.logger import logger diff --git a/backend/program/libaries/symlink.py b/backend/program/libaries/symlink.py index 09da4f84..d5bb71c4 100644 --- a/backend/program/libaries/symlink.py +++ b/backend/program/libaries/symlink.py @@ -1,6 +1,6 @@ import os import re -from pathlib import Path +from typing import Generator from utils.logger import logger from program.settings.manager import settings_manager @@ -10,7 +10,6 @@ Show, Season, Episode, - ItemId ) class SymlinkLibrary: @@ -34,7 +33,7 @@ def validate(self) -> bool: logger.debug("library dir contains %s", ", ".join(folders)) return status - def run(self) -> MediaItem: + def run(self) -> Generator[MediaItem, None, None]: """Create a library from the symlink paths. Return stub items that should be fed into an Indexer to have the rest of the metadata filled in.""" movies = [ diff --git a/backend/program/media/__init__.py b/backend/program/media/__init__.py index 8cdeb86f..a3fe43d9 100644 --- a/backend/program/media/__init__.py +++ b/backend/program/media/__init__.py @@ -1,3 +1,3 @@ -from .item import MediaItem, Episode, Show, Season, Movie -from .container import MediaItemContainer -from .state import States \ No newline at end of file +from .item import MediaItem, Episode, Show, Season, Movie # noqa +from .container import MediaItemContainer # noqa +from .state import States # noqa diff --git a/backend/program/program.py b/backend/program/program.py index fc349698..86623add 100644 --- a/backend/program/program.py +++ b/backend/program/program.py @@ -250,7 +250,7 @@ def process_event_and_collect_coverage( existing_item: MediaItem | None, emitted_by: Service, item: MediaItem -) -> ProcessedEvent: +) -> ProcessedEvent: # type: ignore file_path = inspect.getfile(process_event) # Load the source code and extract executed lines @@ -260,8 +260,8 @@ def process_event_and_collect_coverage( lines, start_line_no = inspect.getsourcelines(process_event) logic_start_line_no = next( i + start_line_no + 1 - for i, l in enumerate(source_lines[start_line_no:]) - if l.strip().startswith("if ") + for i, line in enumerate(source_lines[start_line_no:]) + if line.strip().startswith("if ") ) end_line_no = logic_start_line_no + len(lines) - 1 diff --git a/backend/program/scrapers/__init__.py b/backend/program/scrapers/__init__.py index 703f8cc6..c97e960f 100644 --- a/backend/program/scrapers/__init__.py +++ b/backend/program/scrapers/__init__.py @@ -19,9 +19,9 @@ def __init__(self): } self.initialized = self.validate() - def run(self, item: MediaItem) -> MediaItem | None: + def run(self, item: MediaItem): if not self._can_we_scrape(item): - return None + yield None for service in self.services.values(): if service.initialized: item = next(service.run(item)) diff --git a/backend/program/scrapers/orionoid.py b/backend/program/scrapers/orionoid.py index 1f667db9..5722f8d5 100644 --- a/backend/program/scrapers/orionoid.py +++ b/backend/program/scrapers/orionoid.py @@ -89,7 +89,7 @@ def run(self, item): if item is None or isinstance(item, Show): yield item try: - item = self._scrape_item(item) + yield self._scrape_item(item) except ConnectTimeout: self.minute_limiter.limit_hit() logger.warn("Orionoid connection timeout for item: %s", item.log_string) @@ -104,7 +104,6 @@ def run(self, item): logger.exception( "Orionoid exception for item: %s - Exception: %s", item.log_string, e ) - yield item def _scrape_item(self, item): data, stream_count = self.api_scrape(item) @@ -148,7 +147,8 @@ def construct_url(self, media_type, imdb_id, season=None, episode=None) -> str: if self.is_unlimited: # This can use 2x towards your Orionoid limits. Only use if user is unlimited. params["debridlookup"] = "realdebrid" - params["limitcount"] = 100 + # There are 200 results per page. We probably don't need to go over 200. + params["limitcount"] = 200 if media_type == "show": params["numberseason"] = season diff --git a/backend/program/scrapers/torrentio.py b/backend/program/scrapers/torrentio.py index 4431fb15..66bf1516 100644 --- a/backend/program/scrapers/torrentio.py +++ b/backend/program/scrapers/torrentio.py @@ -51,7 +51,7 @@ def run(self, item): if item is None or isinstance(item, Show): yield item try: - item = self._scrape_item(item) + yield self._scrape_item(item) except RateLimitExceeded: self.minute_limiter.limit_hit() except ConnectTimeout: @@ -63,10 +63,9 @@ def run(self, item): except RequestException as e: self.minute_limiter.limit_hit() logger.warn("Torrentio request exception: %s", e) - except Exception as e: + except Exception: self.minute_limiter.limit_hit() logger.warn("Torrentio exception thrown: %s", traceback.format_exc()) - yield item def _scrape_item(self, item): """Scrape torrentio for the given media item""" diff --git a/backend/program/state_transision.py b/backend/program/state_transition.py similarity index 97% rename from backend/program/state_transision.py rename to backend/program/state_transition.py index 1eedca1b..29d9eddc 100644 --- a/backend/program/state_transision.py +++ b/backend/program/state_transition.py @@ -11,12 +11,12 @@ from utils.logger import logger -def process_event(existing_item: MediaItem | None, emitted_by: Service, item: MediaItem) -> ProcessedEvent: +def process_event(existing_item: MediaItem | None, emitted_by: Service, item: MediaItem) -> ProcessedEvent: # type: ignore """Take the input event, process it, and output items to submit to a Service, and an item to update the container with.""" next_service : Service = None updated_item = item - no_further_processing: ProcessedEvent = (None, None, []) + no_further_processing: ProcessedEvent = (None, None, []) # type: ignore # we always want to get metadata for content items before we compare to the container. # we can't just check if the show exists we have to check if it's complete source_services = (Overseerr, PlexWatchlist, Listrr, Mdblist, SymlinkLibrary) diff --git a/backend/utils/parser.py b/backend/utils/parser.py index 68fb87da..97c0ba30 100644 --- a/backend/utils/parser.py +++ b/backend/utils/parser.py @@ -79,10 +79,8 @@ def _parse(self, item, string) -> dict: "extended": parse.get("extended", False), } - # bandaid for now, this needs to be refactored to make less calls to _parse if item is not None: parsed_data["title_match"] = title_match - parsed_data["fetch"] = self._should_fetch(parsed_data) return parsed_data @@ -103,12 +101,9 @@ def _should_fetch(self, parsed_data: dict) -> bool: # This is where we determine if the item should be fetched # based on the user settings and predefined rules. # Edit with caution. All have to match for the item to be fetched. - # item_language = self._get_item_language(item) return ( parsed_data["resolution"] in self.resolution - and - # any(lang in parsed_data.get("language", item_language) for lang in self.language) and - not parsed_data["is_unwanted_quality"] + and not parsed_data["is_unwanted_quality"] ) def _is_highest_quality(self, parsed_data: dict) -> bool: @@ -124,17 +119,16 @@ def _is_highest_quality(self, parsed_data: dict) -> bool: def _is_dual_audio(self, string) -> bool: """Check if any content in parsed_data has dual audio.""" dual_audio_patterns = [ - re.compile( - r"\bmulti(?:ple)?[ .-]*(?:lang(?:uages?)?|audio|VF2)?\b", re.IGNORECASE - ), - re.compile(r"\btri(?:ple)?[ .-]*(?:audio|dub\w*)\b", re.IGNORECASE), - re.compile(r"\bdual[ .-]*(?:au?$|[aá]udio|line)\b", re.IGNORECASE), - re.compile(r"\bdual\b(?![ .-]*sub)", re.IGNORECASE), - re.compile(r"\b(?:audio|dub(?:bed)?)[ .-]*dual\b", re.IGNORECASE), - re.compile(r"\bengl?(?:sub[A-Z]*)?\b", re.IGNORECASE), - re.compile(r"\beng?sub[A-Z]*\b", re.IGNORECASE), - re.compile(r"\b(?:DUBBED|dublado|dubbing|DUBS?)\b", re.IGNORECASE), + r"\bmulti(?:ple)?[ .-]*(?:lang(?:uages?)?|audio|VF2)?\b", + r"\btri(?:ple)?[ .-]*(?:audio|dub\w*)\b", + r"\bdual[ .-]*(?:au?$|[aá]udio|line)\b", + r"\bdual\b(?![ .-]*sub)", + r"\b(?:audio|dub(?:bed)?)[ .-]*dual\b", + r"\bengl?(?:sub[A-Z]*)?\b", + r"\beng?sub[A-Z]*\b", + r"\b(?:DUBBED|dublado|dubbing|DUBS?)\b", ] + dual_audio_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in dual_audio_patterns] return any(pattern.search(string) for pattern in dual_audio_patterns) @staticmethod @@ -142,53 +136,40 @@ def _is_complete_series(string) -> bool: """Check if string is a `complete series`.""" # Can be used on either movie or show item types series_patterns = [ - re.compile( - r"(?:\bthe\W)?(?:\bcomplete|collection|dvd)?\b[ .]?\bbox[ .-]?set\b", - re.IGNORECASE, - ), - re.compile( - r"(?:\bthe\W)?(?:\bcomplete|collection|dvd)?\b[ .]?\bmini[ .-]?series\b", - re.IGNORECASE, - ), - re.compile( - r"(?:\bthe\W)?(?:\bcomplete|full|all)\b.*\b(?:series|seasons|collection|episodes|set|pack|movies)\b", - re.IGNORECASE, - ), - re.compile( - r"\b(?:series|seasons|movies?)\b.*\b(?:complete|collection)\b", - re.IGNORECASE, - ), - re.compile(r"(?:\bthe\W)?\bultimate\b[ .]\bcollection\b", re.IGNORECASE), - re.compile(r"\bcollection\b.*\b(?:set|pack|movies)\b", re.IGNORECASE), - re.compile(r"\bcollection\b", re.IGNORECASE), - re.compile( - r"duology|trilogy|quadr[oi]logy|tetralogy|pentalogy|hexalogy|heptalogy|anthology|saga", - re.IGNORECASE, - ), + r"(?:\bthe\W)?(?:\bcomplete|collection|dvd)?\b[ .]?\bbox[ .-]?set\b", + r"(?:\bthe\W)?(?:\bcomplete|collection|dvd)?\b[ .]?\bmini[ .-]?series\b", + r"(?:\bthe\W)?(?:\bcomplete|full|all)\b.*\b(?:series|seasons|collection|episodes|set|pack|movies)\b", + r"\b(?:series|seasons|movies?)\b.*\b(?:complete|collection)\b", + r"(?:\bthe\W)?\bultimate\b[ .]\bcollection\b", + r"\bcollection\b.*\b(?:set|pack|movies)\b", + r"\bcollection\b", + r"duology|trilogy|quadr[oi]logy|tetralogy|pentalogy|hexalogy|heptalogy|anthology|saga", ] + series_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in series_patterns] return any(pattern.search(string) for pattern in series_patterns) @staticmethod def _is_unwanted_quality(string) -> bool: """Check if string has an 'unwanted' quality. Default to False.""" - unwanted_patterns = [ - re.compile( - r"\b(?:H[DQ][ .-]*)?CAM(?:H[DQ])?(?:[ .-]*Rip)?\b", re.IGNORECASE - ), - re.compile(r"\b(?:H[DQ][ .-]*)?S[ .-]*print\b", re.IGNORECASE), - re.compile(r"\b(?:HD[ .-]*)?T(?:ELE)?S(?:YNC)?(?:Rip)?\b", re.IGNORECASE), - re.compile(r"\b(?:HD[ .-]*)?T(?:ELE)?C(?:INE)?(?:Rip)?\b", re.IGNORECASE), - re.compile(r"\bP(?:re)?DVD(?:Rip)?\b", re.IGNORECASE), - re.compile(r"\b(?:DVD?|BD|BR)?[ .-]*Scr(?:eener)?\b", re.IGNORECASE), - re.compile(r"\bVHS\b", re.IGNORECASE), - re.compile(r"\bHD[ .-]*TV(?:Rip)?\b", re.IGNORECASE), - re.compile(r"\bDVB[ .-]*(?:Rip)?\b", re.IGNORECASE), - re.compile(r"\bSAT[ .-]*Rips?\b", re.IGNORECASE), - re.compile(r"\bTVRips?\b", re.IGNORECASE), - re.compile(r"\bR5\b", re.IGNORECASE), - re.compile(r"\b(DivX|XviD)\b", re.IGNORECASE), + unwanted_quality = [ + r"\b(?:H[DQ][ .-]*)?CAM(?:H[DQ])?(?:[ .-]*Rip)?\b", + r"\b(?:H[DQ][ .-]*)?S[ .-]*print\b", + r"\b(?:HD[ .-]*)?T(?:ELE)?S(?:YNC)?(?:Rip)?\b", + r"\b(?:HD[ .-]*)?T(?:ELE)?C(?:INE)?(?:Rip)?\b", + r"\bP(?:re)?DVD(?:Rip)?\b", + r"\b(?:DVD?|BD|BR)?[ .-]*Scr(?:eener)?\b", + r"\bVHS\b", + r"\bHD[ .-]*TV(?:Rip)?\b", + r"\bDVB[ .-]*(?:Rip)?\b", + r"\bSAT[ .-]*Rips?\b", + r"\bTVRips?\b", + r"\bR5|R6\b", + r"\b(DivX|XviD)\b", + r"\b(?:Deleted[ .-]*)?Scene(?:s)?\b", + r"\bTrailers?\b", ] - return any(pattern.search(string) for pattern in unwanted_patterns) + unwanted_quality = [re.compile(pattern, re.IGNORECASE) for pattern in unwanted_quality] + return any(pattern.search(string) for pattern in unwanted_quality) def check_for_title_match(self, item, parsed_title, threshold=90) -> bool: """Check if the title matches PTN title using fuzzy matching.""" @@ -197,14 +178,11 @@ def check_for_title_match(self, item, parsed_title, threshold=90) -> bool: target_title = item.parent.title elif item.type == "episode": target_title = item.parent.parent.title - match_score = fuzz.ratio(parsed_title.lower(), target_title.lower()) - if match_score >= threshold: - return True - return False + return bool(fuzz.ratio(parsed_title.lower(), target_title.lower()) >= threshold) def _get_item_language(self, item) -> str: """Get the language of the item.""" - # This is crap. Need to switch to using a dict instead. + # TODO: This is crap. Need to switch to ranked sorting instead. if item.type == "season": if item.parent.language == "en": if item.parent.is_anime: diff --git a/backend/utils/request.py b/backend/utils/request.py index bd0052f1..c0d82ffb 100644 --- a/backend/utils/request.py +++ b/backend/utils/request.py @@ -32,7 +32,7 @@ def __init__(self, response: requests.Response, response_type=SimpleNamespace): def handle_response(self, response: requests.Response): """Handle different types of responses""" - if not self.is_ok and self.status_code not in [404, 429, 509, 520, 522]: + if not self.is_ok and self.status_code not in [404, 429, 502, 509, 520, 522]: logger.error("Error: %s %s", response.status_code, response.content) if self.status_code in [520, 522]: # Cloudflare error from Torrentio diff --git a/frontend/package.json b/frontend/package.json index 9fb07f5c..1c0bbeba 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,8 +19,8 @@ "@types/luxon": "^3.3.7", "@types/nprogress": "^0.2.3", "@types/uuid": "^9.0.7", - "@typescript-eslint/eslint-plugin": "^7.0.0", - "@typescript-eslint/parser": "^6.20.0", + "@typescript-eslint/eslint-plugin": "^7.1.0", + "@typescript-eslint/parser": "^7.1.0", "autoprefixer": "^10.4.14", "eslint": "^8.28.0", "eslint-config-prettier": "^9.1.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 671d9e8e..578b4bde 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -74,11 +74,11 @@ devDependencies: specifier: ^9.0.7 version: 9.0.7 '@typescript-eslint/eslint-plugin': - specifier: ^7.0.0 - version: 7.0.0(@typescript-eslint/parser@6.20.0)(eslint@8.56.0)(typescript@5.3.3) + specifier: ^7.1.0 + version: 7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/parser': - specifier: ^6.20.0 - version: 6.20.0(eslint@8.56.0)(typescript@5.3.3) + specifier: ^7.1.0 + version: 7.1.0(eslint@8.56.0)(typescript@5.3.3) autoprefixer: specifier: ^10.4.14 version: 10.4.17(postcss@8.4.33) @@ -830,11 +830,11 @@ packages: resolution: {integrity: sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==} dev: true - /@typescript-eslint/eslint-plugin@7.0.0(@typescript-eslint/parser@6.20.0)(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-M72SJ0DkcQVmmsbqlzc6EJgb/3Oz2Wdm6AyESB4YkGgCxP8u5jt5jn4/OBMPK3HLOxcttZq5xbBBU7e2By4SZQ==} + /@typescript-eslint/eslint-plugin@7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + '@typescript-eslint/parser': ^7.0.0 eslint: ^8.56.0 typescript: '*' peerDependenciesMeta: @@ -842,11 +842,11 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 6.20.0(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/scope-manager': 7.0.0 - '@typescript-eslint/type-utils': 7.0.0(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/utils': 7.0.0(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 7.0.0 + '@typescript-eslint/parser': 7.1.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 7.1.0 + '@typescript-eslint/type-utils': 7.1.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/utils': 7.1.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 7.1.0 debug: 4.3.4 eslint: 8.56.0 graphemer: 1.4.0 @@ -859,20 +859,20 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@6.20.0(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-bYerPDF/H5v6V76MdMYhjwmwgMA+jlPVqjSDq2cRqMi8bP5sR3Z+RLOiOMad3nsnmDVmn2gAFCyNgh/dIrfP/w==} + /@typescript-eslint/parser@7.1.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0 + eslint: ^8.56.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 6.20.0 - '@typescript-eslint/types': 6.20.0 - '@typescript-eslint/typescript-estree': 6.20.0(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 6.20.0 + '@typescript-eslint/scope-manager': 7.1.0 + '@typescript-eslint/types': 7.1.0 + '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 7.1.0 debug: 4.3.4 eslint: 8.56.0 typescript: 5.3.3 @@ -880,24 +880,16 @@ packages: - supports-color dev: true - /@typescript-eslint/scope-manager@6.20.0: - resolution: {integrity: sha512-p4rvHQRDTI1tGGMDFQm+GtxP1ZHyAh64WANVoyEcNMpaTFn3ox/3CcgtIlELnRfKzSs/DwYlDccJEtr3O6qBvA==} - engines: {node: ^16.0.0 || >=18.0.0} - dependencies: - '@typescript-eslint/types': 6.20.0 - '@typescript-eslint/visitor-keys': 6.20.0 - dev: true - - /@typescript-eslint/scope-manager@7.0.0: - resolution: {integrity: sha512-IxTStwhNDPO07CCrYuAqjuJ3Xf5MrMaNgbAZPxFXAUpAtwqFxiuItxUaVtP/SJQeCdJjwDGh9/lMOluAndkKeg==} + /@typescript-eslint/scope-manager@7.1.0: + resolution: {integrity: sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 7.0.0 - '@typescript-eslint/visitor-keys': 7.0.0 + '@typescript-eslint/types': 7.1.0 + '@typescript-eslint/visitor-keys': 7.1.0 dev: true - /@typescript-eslint/type-utils@7.0.0(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-FIM8HPxj1P2G7qfrpiXvbHeHypgo2mFpFGoh5I73ZlqmJOsloSa1x0ZyXCer43++P1doxCgNqIOLqmZR6SOT8g==} + /@typescript-eslint/type-utils@7.1.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^8.56.0 @@ -906,8 +898,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 7.0.0(typescript@5.3.3) - '@typescript-eslint/utils': 7.0.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3) + '@typescript-eslint/utils': 7.1.0(eslint@8.56.0)(typescript@5.3.3) debug: 4.3.4 eslint: 8.56.0 ts-api-utils: 1.0.3(typescript@5.3.3) @@ -916,18 +908,13 @@ packages: - supports-color dev: true - /@typescript-eslint/types@6.20.0: - resolution: {integrity: sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==} - engines: {node: ^16.0.0 || >=18.0.0} - dev: true - - /@typescript-eslint/types@7.0.0: - resolution: {integrity: sha512-9ZIJDqagK1TTs4W9IyeB2sH/s1fFhN9958ycW8NRTg1vXGzzH5PQNzq6KbsbVGMT+oyyfa17DfchHDidcmf5cg==} + /@typescript-eslint/types@7.1.0: + resolution: {integrity: sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==} engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/typescript-estree@6.20.0(typescript@5.3.3): - resolution: {integrity: sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==} + /@typescript-eslint/typescript-estree@7.1.0(typescript@5.3.3): + resolution: {integrity: sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' @@ -935,8 +922,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 6.20.0 - '@typescript-eslint/visitor-keys': 6.20.0 + '@typescript-eslint/types': 7.1.0 + '@typescript-eslint/visitor-keys': 7.1.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -948,30 +935,8 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree@7.0.0(typescript@5.3.3): - resolution: {integrity: sha512-JzsOzhJJm74aQ3c9um/aDryHgSHfaX8SHFIu9x4Gpik/+qxLvxUylhTsO9abcNu39JIdhY2LgYrFxTii3IajLA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 7.0.0 - '@typescript-eslint/visitor-keys': 7.0.0 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.5.4 - ts-api-utils: 1.0.3(typescript@5.3.3) - typescript: 5.3.3 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/utils@7.0.0(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-kuPZcPAdGcDBAyqDn/JVeJVhySvpkxzfXjJq1X1BFSTYo1TTuo4iyb937u457q4K0In84p6u2VHQGaFnv7VYqg==} + /@typescript-eslint/utils@7.1.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^8.56.0 @@ -979,9 +944,9 @@ packages: '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.6 - '@typescript-eslint/scope-manager': 7.0.0 - '@typescript-eslint/types': 7.0.0 - '@typescript-eslint/typescript-estree': 7.0.0(typescript@5.3.3) + '@typescript-eslint/scope-manager': 7.1.0 + '@typescript-eslint/types': 7.1.0 + '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3) eslint: 8.56.0 semver: 7.5.4 transitivePeerDependencies: @@ -989,19 +954,11 @@ packages: - typescript dev: true - /@typescript-eslint/visitor-keys@6.20.0: - resolution: {integrity: sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==} - engines: {node: ^16.0.0 || >=18.0.0} - dependencies: - '@typescript-eslint/types': 6.20.0 - eslint-visitor-keys: 3.4.3 - dev: true - - /@typescript-eslint/visitor-keys@7.0.0: - resolution: {integrity: sha512-JZP0uw59PRHp7sHQl3aF/lFgwOW2rgNVnXUksj1d932PMita9wFBd3621vHQRDvHwPsSY9FMAAHVc8gTvLYY4w==} + /@typescript-eslint/visitor-keys@7.1.0: + resolution: {integrity: sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 7.0.0 + '@typescript-eslint/types': 7.1.0 eslint-visitor-keys: 3.4.3 dev: true diff --git a/makefile b/makefile index 323ebef3..9cc12fda 100644 --- a/makefile +++ b/makefile @@ -1,4 +1,4 @@ -.PHONY: help start stop restart logs exec sc ec update frontend backend +.PHONY: help install run start stop logs lint test pr-ready # Detect operating system ifeq ($(OS),Windows_NT) @@ -13,25 +13,24 @@ help: @echo Iceberg Local Development Environment @echo ------------------------------------------------------------------------- @echo install : Install the required packages + @echo run : Run the Iceberg backend @echo start : Build and run the Iceberg container @echo stop : Stop and remove the Iceberg container and image - @echo restart : Restart the Iceberg container (without rebuilding image) - @echo exec : Open a shell inside the Iceberg container @echo logs : Show the logs of the Iceberg container - @echo sc : Show the contents of the settings.json file inside the Iceberg container - @echo ec : Edit the settings.json file inside the Iceberg container - @echo update : Update this repository from GitHub and rebuild image - @echo frontend : Start the frontend development server - @echo backend : Start the backend development server + @echo lint : Run the linter and type checker + @echo test : Run the tests + @echo pr-ready : Run the linter and tests @echo ------------------------------------------------------------------------- install: - @python3 -m pip install -r requirements.txt --break-system-packages + @pip install -r requirements.txt --upgrade --break-system-packages + +run: + @python3 backend/main.py start: stop @docker build -t iceberg:latest -f Dockerfile . @docker run -d --name iceberg --hostname iceberg --net host -e PUID=1000 -e PGID=1000 -v $(DATA_PATH):/iceberg/data -v /mnt:/mnt iceberg:latest - @echo Iceberg is running on http://localhost:3000/ @docker logs iceberg -f stop: @@ -39,33 +38,14 @@ stop: @-docker rm iceberg --force @-docker rmi iceberg:latest --force -restart: - @-docker restart iceberg - @echo Iceberg Frontend is running on http://localhost:3000/status/ - @echo Iceberg Backend is running on http://localhost:8080/items/ - @docker logs iceberg -f - logs: @docker logs iceberg -f -exec: - @docker exec -it iceberg /bin/bash - -sc: - @docker exec -it iceberg /bin/bash -c "cat /iceberg/data/settings.json" - -ec: - @docker exec -it iceberg /bin/bash -c "vim /iceberg/data/settings.json" - -update: - @echo Not implemented yet - # @-git pull --rebase - # @make start +lint: + ruff check backend + pyright backend -frontend: - @echo Starting Frontend... - @cd frontend && pnpm install && pnpm run build && pnpm run preview --host +test: + cd backend/tests && pytest -vv -backend: - @echo Starting Backend... - @cd backend && python main.py \ No newline at end of file +pr-ready: lint test diff --git a/requirements.txt b/requirements.txt index 5b63c520..e05b2e26 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,3 +13,5 @@ apscheduler watchdog coverage deepdiff +pyright +ruff