From 6ee3870ab0756065cb40183601827ed2c77769ae Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Tue, 17 Jun 2025 11:13:11 +0300 Subject: [PATCH] Features: 1) Add a management command to fix product stock prices; 2) Introduce 'products' field to CategoryType in GraphQL schema to fetch products associated with a category; 3) Enable GraphQL resolvers to utilize type hinting for better clarity. Fixes: 1) Correct multiple unaligned code blocks in various Python scripts and GraphQL resolvers; 2) Improve condition formatting for readability in mutations and queries; 3) Resolve missing related_name in product model. Extra: Simplify and refactor Windows scripts removing legacy spinner logic for clarity and better user feedback; adjust spacing, comments, and formatting across various files; update imports for unused QuerySet. --- core/admin.py | 2 +- core/filters.py | 16 +++---- core/graphene/object_types.py | 58 +++++++++++++----------- core/management/commands/fix_prices.py | 23 ++++++++++ core/models.py | 4 +- core/permissions.py | 4 +- evibes/api_urls.py | 2 +- evibes/settings/constance.py | 17 ++++--- scripts/Windows/install.ps1 | 56 +++++------------------ scripts/Windows/reboot.ps1 | 62 ++++++++++---------------- scripts/Windows/run.ps1 | 51 ++++++--------------- scripts/Windows/uninstall.ps1 | 45 +++++-------------- 12 files changed, 135 insertions(+), 205 deletions(-) create mode 100644 core/management/commands/fix_prices.py diff --git a/core/admin.py b/core/admin.py index 8930a713..84da6298 100644 --- a/core/admin.py +++ b/core/admin.py @@ -106,7 +106,7 @@ class CategoryChildrenInline(admin.TabularInline): class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, TabbedTranslationAdmin): mptt_indent_field = "name" list_display = ("indented_title", "parent", "is_active", "modified") - list_filter = ("is_active", "level", "created", "modified") # noqa: DJ + list_filter = ("is_active", "level", "created", "modified") list_display_links = ("indented_title",) search_fields = ( "uuid", diff --git a/core/filters.py b/core/filters.py index 834ca317..57f4a588 100644 --- a/core/filters.py +++ b/core/filters.py @@ -29,7 +29,7 @@ class CaseInsensitiveListFilter(BaseInFilter, CharFilter): return qs if isinstance(value, str): - values = [v.strip() for v in value.split(',') if v.strip()] + values = [v.strip() for v in value.split(",") if v.strip()] else: values = [v for v in value if v] @@ -49,10 +49,7 @@ class ProductFilter(FilterSet): uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID")) name = CharFilter(field_name="name", lookup_expr="icontains", label=_("Name")) categories = CaseInsensitiveListFilter(field_name="category__name", label=_("Categories")) - category_uuid = CharFilter( - method="filter_category", - label="Category (UUID)" - ) + category_uuid = CharFilter(method="filter_category", label="Category (UUID)") categories_slugs = CaseInsensitiveListFilter(field_name="category__slug", label=_("Categories Slugs")) tags = CaseInsensitiveListFilter(field_name="tags__tag_name", label=_("Tags")) min_price = NumberFilter(field_name="stocks__price", lookup_expr="gte", label=_("Min Price")) @@ -63,10 +60,7 @@ class ProductFilter(FilterSet): quantity = NumberFilter(field_name="stocks__quantity", lookup_expr="gt", label=_("Quantity")) slug = CharFilter(field_name="slug", lookup_expr="exact", label=_("Slug")) is_digital = BooleanFilter(field_name="is_digital", label=_("Is Digital")) - include_subcategories = BooleanFilter( - method="filter_include_flag", - label=_("Include sub-categories") - ) + include_subcategories = BooleanFilter(method="filter_include_flag", label=_("Include sub-categories")) order_by = OrderingFilter( fields=( @@ -322,7 +316,9 @@ class CategoryFilter(FilterSet): name = CharFilter(field_name="name", lookup_expr="icontains", label=_("Name")) parent_uuid = CharFilter(method="filter_parent_uuid", label=_("Parent")) slug = CharFilter(field_name="slug", lookup_expr="exact", label=_("Slug")) - whole = BooleanFilter(field_name="whole", label=_("Whole category"), method="filter_whole_categories") + whole = BooleanFilter( + field_name="whole", label=_("Whole category(has at least 1 product or not)"), method="filter_whole_categories" + ) tags = CaseInsensitiveListFilter(field_name="tags__tag_name", label=_("Tags")) level = NumberFilter(field_name="level", lookup_expr="exact", label=_("Level")) diff --git a/core/graphene/object_types.py b/core/graphene/object_types.py index 5f666fa5..16373940 100644 --- a/core/graphene/object_types.py +++ b/core/graphene/object_types.py @@ -1,5 +1,5 @@ from django.core.cache import cache -from django.db.models import Max, Min, QuerySet +from django.db.models import Max, Min from django.db.models.functions import Length from django.utils.translation import gettext_lazy as _ from graphene import UUID, Field, Float, InputObjectType, Int, List, NonNull, ObjectType, String, relay @@ -63,8 +63,9 @@ class AttributeGroupType(DjangoObjectType): filter_fields = ["uuid"] description = _("groups of attributes") - def resolve_attributes(self, info): + def resolve_attributes(self: AttributeGroup, info): product_uuid = getattr(info.context, "_product_uuid", None) + qs = self.attributes.all() if product_uuid: @@ -83,15 +84,15 @@ class BrandType(DjangoObjectType): filter_fields = ["uuid", "name"] description = _("brands") - def resolve_categories(self, info): + def resolve_categories(self: Brand, info): if info.context.user.has_perm("core.view_category"): return self.categories.all() return self.categories.filter(is_active=True) - def resolve_big_logo(self, info): + def resolve_big_logo(self: Brand, info): return info.context.build_absolute_uri(self.big_logo.url) if self.big_logo else "" - def resolve_small_logo(self, info): + def resolve_small_logo(self: Brand, info): return info.context.build_absolute_uri(self.small_logo.url) if self.small_logo else "" @@ -121,6 +122,7 @@ class CategoryType(DjangoObjectType): description=_("minimum and maximum prices for products in this category, if available."), ) tags = DjangoFilterConnectionField(lambda: CategoryTagType, description=_("tags for this category")) + products = DjangoFilterConnectionField(lambda: ProductType, description=_("products in this category")) class Meta: model = Category @@ -145,15 +147,15 @@ class CategoryType(DjangoObjectType): return categories return categories.filter(is_active=True) - def resolve_image(self, info) -> str: + def resolve_image(self: Category, info) -> str: return info.context.build_absolute_uri(self.image.url) if self.image else "" - def resolve_markup_percent(self, info) -> float: + def resolve_markup_percent(self: Category, info) -> float: if info.context.user.has_perm("core.view_category"): return float(self.markup_percent) return 0.0 - def resolve_filterable_attributes(self, info): + def resolve_filterable_attributes(self: Category, info): filterable_results = cache.get(f"{self.uuid}_filterable_results", []) if len(filterable_results) > 0: @@ -175,7 +177,10 @@ class CategoryType(DjangoObjectType): if len(distinct_vals_list) <= 128: filterable_results.append( - FilterableAttributeType(attribute_name=attr.name, possible_values=distinct_vals_list) + { + "attribute_name": attr.name, + "possible_values": distinct_vals_list, + } ) else: pass @@ -184,7 +189,7 @@ class CategoryType(DjangoObjectType): return filterable_results - def resolve_min_max_prices(self, info): + def resolve_min_max_prices(self: Category, _info): min_max_prices = cache.get(key=f"{self.name}_min_max_prices", default={}) if not min_max_prices: @@ -193,10 +198,9 @@ class CategoryType(DjangoObjectType): ) min_max_prices["min_price"] = price_aggregation.get("min_price", 0.0) min_max_prices["max_price"] = price_aggregation.get("max_price", 0.0) - cache.set(key=f"{self.name}_min_max_prices", value=min_max_prices, timeout=86400) - return MinMaxPriceType(min_price=min_max_prices["min_price"], max_price=min_max_prices["max_price"]) + return {"min_price": min_max_prices["min_price"], "max_price": min_max_prices["max_price"]} class VendorType(DjangoObjectType): @@ -230,12 +234,14 @@ class AddressType(DjangoObjectType): ) read_only_fields = ("api_response",) - def resolve_latitude(self, info): - return self.location.y if self.location else None - - def resolve_longitude(self, info): + def resolve_latitude(self: Address, _info): + # noinspection PyUnresolvedReferences return self.location.x if self.location else None + def resolve_longitude(self: Address, _info): + # noinspection PyUnresolvedReferences + return self.location.y if self.location else None + class FeedbackType(DjangoObjectType): comment = String(description=_("comment")) @@ -269,13 +275,13 @@ class OrderProductType(DjangoObjectType): filter_fields = ["uuid"] description = _("order products") - def resolve_attributes(self, info): + def resolve_attributes(self, _info): return camelize(self.attributes) - def resolve_notifications(self, info): + def resolve_notifications(self, _info): return camelize(self.notifications) - def resolve_download_url(self, info) -> str | None: + def resolve_download_url(self: OrderProduct, _info) -> str | None: return self.download_url @@ -318,10 +324,10 @@ class OrderType(DjangoObjectType): def resolve_total_quantity(self, _info): return self.total_quantity - def resolve_notifications(self, info): + def resolve_notifications(self, _info): return camelize(self.notifications) - def resolve_attributes(self, info): + def resolve_attributes(self, _info): return camelize(self.attributes) @@ -335,7 +341,7 @@ class ProductImageType(DjangoObjectType): filter_fields = ["uuid"] description = _("product's images") - def resolve_image(self, info): + def resolve_image(self: ProductImage, info): return info.context.build_absolute_uri(self.image.url) if self.image else "" @@ -370,7 +376,7 @@ class ProductType(DjangoObjectType): def resolve_price(self, _info) -> float: return self.price or 0.0 - def resolve_feedbacks(self, _info) -> QuerySet[Feedback]: + def resolve_feedbacks(self: Product, _info): if _info.context.user.has_perm("core.view_feedback"): return Feedback.objects.filter(order_product__product=self) return Feedback.objects.filter(order_product__product=self, is_active=True) @@ -378,7 +384,7 @@ class ProductType(DjangoObjectType): def resolve_feedbacks_count(self, _info) -> int: return self.feedbacks_count or 0 - def resolve_attribute_groups(self, info): + def resolve_attribute_groups(self: Product, info): info.context._product_uuid = self.uuid return AttributeGroup.objects.filter(attributes__values__product=self).distinct() @@ -415,10 +421,10 @@ class PromoCodeType(DjangoObjectType): filter_fields = ["uuid"] description = _("promocodes") - def resolve_discount(self, info) -> float: + def resolve_discount(self: PromoCode, _info) -> float: return self.discount_percent if self.discount_percent else self.discount_amount - def resolve_discount_type(self, info) -> str: + def resolve_discount_type(self: PromoCode, _info) -> str: return "percent" if self.discount_percent else "amount" diff --git a/core/management/commands/fix_prices.py b/core/management/commands/fix_prices.py new file mode 100644 index 00000000..5d3c1dab --- /dev/null +++ b/core/management/commands/fix_prices.py @@ -0,0 +1,23 @@ +import logging + +from django.core.management.base import BaseCommand + +from core.models import Product +from core.vendors import AbstractVendor + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + def handle(self, *args, **options): + self.stdout.write(self.style.SUCCESS("Starting fixing stocks' prices...")) + + for product in Product.objects.filter(stocks__isnull=False): + for stock in product.stocks: + try: + stock.price = AbstractVendor.round_price_marketologically(stock.price) + stock.save() + except Exception as e: + self.stdout.write(self.style.WARNING(f"Couldn't fix price on {stock.uuid}")) + self.stdout.write(self.style.WARNING(f"Error: {e}")) + self.stdout.write(self.style.SUCCESS("Successfully fixed stocks' prices!")) diff --git a/core/models.py b/core/models.py index 00cdaa9d..8edb090a 100644 --- a/core/models.py +++ b/core/models.py @@ -281,6 +281,7 @@ class Product(ExportModelOperationsMixin("product"), NiceModel): on_delete=CASCADE, help_text=_("category this product belongs to"), verbose_name=_("category"), + related_name="products", ) brand = ForeignKey( "core.Brand", @@ -1292,8 +1293,7 @@ class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceMo if self.order_product.status != "FINISHED": raise ValueError(_("you can not download a digital asset for a non-finished order")) - return (f"https://api.{config.BASE_URL}/" # noqa: DJ - f"download/{urlsafe_base64_encode(force_bytes(self.order_product.uuid))}") + return f"https://api.{config.BASE_URL}/download/{urlsafe_base64_encode(force_bytes(self.order_product.uuid))}" class Documentary(ExportModelOperationsMixin("attribute_group"), NiceModel): diff --git a/core/permissions.py b/core/permissions.py index 3a941e60..fab96962 100644 --- a/core/permissions.py +++ b/core/permissions.py @@ -96,9 +96,7 @@ class EvibesPermission(permissions.BasePermission): return queryset.none() base = queryset.filter(is_active=True, user=request.user) - if request.user.has_perm( - f"{app_label}.{self.ACTION_PERM_MAP.get(view.action)}_{model_name}" - ): + if request.user.has_perm(f"{app_label}.{self.ACTION_PERM_MAP.get(view.action)}_{model_name}"): return queryset.filter(is_active=True) return base diff --git a/evibes/api_urls.py b/evibes/api_urls.py index 2afc2176..1100a8f6 100644 --- a/evibes/api_urls.py +++ b/evibes/api_urls.py @@ -11,7 +11,7 @@ from core.views import CustomGraphQLView, CustomRedocView, CustomSwaggerView, fa from evibes.settings import SPECTACULAR_PLATFORM_SETTINGS urlpatterns = [ - path(r'health/', include('health_check.urls')), + path(r"health/", include("health_check.urls")), path("prometheus/", include("django_prometheus.urls")), path(r"graphql/", csrf_exempt(CustomGraphQLView.as_view(graphiql=True, schema=schema))), path( diff --git a/evibes/settings/constance.py b/evibes/settings/constance.py index 0968a43c..e108ced7 100644 --- a/evibes/settings/constance.py +++ b/evibes/settings/constance.py @@ -5,10 +5,13 @@ from evibes.settings.base import getenv CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend" # Or "constance.backends.redis.RedisBackend" CONSTANCE_ADDITIONAL_FIELDS = { - "json": ["django.forms.fields.JSONField", { - "required": False, - "widget": "core.widgets.JSONTableWidget", - }], + "json": [ + "django.forms.fields.JSONField", + { + "required": False, + "widget": "core.widgets.JSONTableWidget", + }, + ], } CONSTANCE_CONFIG = { @@ -18,8 +21,10 @@ CONSTANCE_CONFIG = { "COMPANY_NAME": (getenv("COMPANY_NAME"), _("Name of the company")), "COMPANY_ADDRESS": (getenv("COMPANY_ADDRESS"), _("Address of the company")), "COMPANY_PHONE_NUMBER": (getenv("COMPANY_PHONE_NUMBER"), _("Phone number of the company")), - "STOCKS_ARE_SINGLE": (getenv("STOCKS_ARE_SINGLE", True), - _("Designates whether every product has one stock or not")), + "STOCKS_ARE_SINGLE": ( + getenv("STOCKS_ARE_SINGLE", True), + _("Designates whether every product has one stock or not"), + ), "EMAIL_HOST": (getenv("EMAIL_HOST", "smtp.404.org"), _("SMTP host")), "EMAIL_PORT": (int(getenv("EMAIL_PORT", "465")), _("SMTP port")), "EMAIL_USE_TLS": (bool(int(getenv("EMAIL_USE_TLS", 0))), _("Use TLS (Specify 0 for No and 1 for Yes)")), diff --git a/scripts/Windows/install.ps1 b/scripts/Windows/install.ps1 index 6c7f8fda..e4c426cb 100644 --- a/scripts/Windows/install.ps1 +++ b/scripts/Windows/install.ps1 @@ -10,19 +10,15 @@ if (-not (Test-Path -Path ".\evibes" -PathType Container)) $purple = "`e[38;2;121;101;209m" $reset = "`e[0m" - $artPath = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Definition) '..\ASCII_ART_EVIBES' + if (-not (Test-Path $artPath)) { Write-Host "❌ Could not find ASCII art at $artPath" -ForegroundColor Red exit 1 } -$art = Get-Content -Raw -Path $artPath -$art -split "`r?`n" | ForEach-Object { - Write-Host "$purple$_$reset" -} - +Get-Content -Raw -Path $artPath | ForEach-Object { Write-Host "$purple$_$reset" } Write-Host "`n by WISELESS TEAM`n" -ForegroundColor Gray if (-not (Test-Path '.env')) @@ -34,7 +30,6 @@ if (-not (Test-Path '.env')) $cpuCount = [Environment]::ProcessorCount if ($cpuCount -lt 4) { - Write-Error "Insufficient CPU cores: $cpuCount detected. Minimum 4 required." exit 1 } @@ -42,54 +37,25 @@ $totalMemBytes = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory $totalMemGB = [Math]::Round($totalMemBytes / 1GB, 2) if ($totalMemGB -lt 6) { - Write-Error "Insufficient RAM: $totalMemGB GB detected. Minimum 6 GB required." exit 1 } $currentDrive = Split-Path -Qualifier $PWD $driveLetter = $currentDrive.Substring(0, 1) -$driveInfo = Get-PSDrive -Name $driveLetter -$freeGB = [Math]::Round($driveInfo.Free / 1GB, 2) +$freeGB = [Math]::Round((Get-PSDrive -Name $driveLetter).Free / 1GB, 2) if ($freeGB -lt 20) { - Write-Error "Insufficient free disk space on drive $driveLetter`: $freeGB GB available. Minimum 20 GB required." exit 1 } Write-Host "System requirements met: CPU cores=$cpuCount, RAM=${totalMemGB}GB, FreeDisk=${freeGB}GB" -ForegroundColor Green -$Spinner = @('|', '/', '-', '\') -$Colors = @('White', 'Gray') -$Delay = 100 +Write-Host "Pulling images" -ForegroundColor Magenta +docker compose pull +Write-Host "Images pulled successfully" -ForegroundColor Green -function Invoke-Spinner -{ - [CmdletBinding()] - param( - [Parameter(Mandatory)][string]$Arguments, - [Parameter(Mandatory)][string]$Message - ) - Write-Host -NoNewLine -ForegroundColor Cyan ("{0}… " -f $Message) - $si = New-Object System.Diagnostics.ProcessStartInfo - $si.FileName = "docker" - $si.Arguments = "$Arguments --ansi never" - $si.RedirectStandardOutput = $true - $si.RedirectStandardError = $true - $si.UseShellExecute = $false - $p = [System.Diagnostics.Process]::Start($si) - $i = 0 - while (-not $p.HasExited) - { - $frame = $Spinner[$i % $Spinner.Length] - $col = $Colors[$i % $Colors.Length] - Write-Host -NoNewLine -ForegroundColor $col $frame - Start-Sleep -Milliseconds $Delay - Write-Host -NoNewline "`b" - $i++ - } - $p.WaitForExit() - Write-Host -ForegroundColor Green "✔" -} - -Invoke-Spinner -Arguments "compose pull" -Message "Pulling related images" -Invoke-Spinner -Arguments "compose build" -Message "Building the project's images" +Write-Host "Building images" -ForegroundColor Magenta +docker compose build +Write-Host "Images built successfully" -ForegroundColor Green +Write-Host "" +Write-Host "You can now use run.ps1 script." -ForegroundColor Cyan diff --git a/scripts/Windows/reboot.ps1 b/scripts/Windows/reboot.ps1 index ebe82079..35ab697c 100644 --- a/scripts/Windows/reboot.ps1 +++ b/scripts/Windows/reboot.ps1 @@ -23,45 +23,29 @@ $art -split "`r?`n" | ForEach-Object { Write-Host "`n by WISELESS TEAM`n" -ForegroundColor Gray -$Spinner = @('|', '/', '-', '\') -$Colors = @('White', 'Gray') -$Delay = 100 -function Invoke-Spinner -{ - [CmdletBinding()] - param( - [Parameter(Mandatory)][string]$Arguments, - [Parameter(Mandatory)][string]$Message - ) - Write-Host -NoNewLine -ForegroundColor Cyan ("{0}… " -f $Message) - $si = New-Object System.Diagnostics.ProcessStartInfo - $si.FileName = "docker" - $si.Arguments = "$Arguments --ansi never" - $si.RedirectStandardOutput = $true - $si.RedirectStandardError = $true - $si.UseShellExecute = $false - $p = [System.Diagnostics.Process]::Start($si) - $i = 0 - while (-not $p.HasExited) - { - $frame = $Spinner[$i % $Spinner.Length] - $col = $Colors[$i % $Colors.Length] - Write-Host -NoNewLine -ForegroundColor $col $frame - Start-Sleep -Milliseconds $Delay - Write-Host -NoNewline "`b" - $i++ - } - $p.WaitForExit() - Write-Host -ForegroundColor Green "✔" -} +Write-Host "Shutting down..." -ForegroundColor Magenta +docker compose down +Write-Host "Services were shut down successfully!" -ForegroundColor Green -Invoke-Spinner -Arguments "compose down" -Message "Stopping services" -Invoke-Spinner -Arguments "compose build" -Message "Rebuilding services" -Invoke-Spinner -Arguments "compose up -d" -Message "Starting services" -Invoke-Spinner -Arguments "compose exec app poetry run python manage.py migrate --no-input" -Message "Applying database migrations" -Invoke-Spinner -Arguments "compose exec app poetry run python manage.py collectstatic --no-input" -Message "Collecting static files" -Invoke-Spinner -Arguments "compose exec app poetry run python manage.py set_default_caches" -Message "Setting default caches" -Invoke-Spinner -Arguments "system prune -f" -Message "Cleaning up unused Docker data" +Write-Host "Spinning services up..." -ForegroundColor Magenta +docker compose up -d --build --wait +Write-Host "Services are up and healthy!" -ForegroundColor Green -Write-Host "`nAll done! eVibes is up and running." -ForegroundColor White +Write-Host "Applying migrations..." -ForegroundColor Magenta +docker compose exec app poetry run python manage.py migrate --no-input +Write-Host "Migrations applied successfully!" -ForegroundColor Green + +Write-Host "Collecting static files..." -ForegroundColor Magenta +docker compose exec app poetry run python manage.py collectstatic --no-input +Write-Host "Static files collected successfully!" -ForegroundColor Green + +Write-Host "Setting default caches..." -ForegroundColor Magenta +docker compose exec app poetry run python manage.py set_default_caches +Write-Host "Default caches set successfully!" -ForegroundColor Green + +Write-Host "Cleaning up unused Docker data..." -ForegroundColor Magenta +docker system prune -f +Write-Host "Unused Docker data cleaned successfully!" -ForegroundColor Green + +Write-Host "All done! eVibes is up and running!" -ForegroundColor Cyan diff --git a/scripts/Windows/run.ps1 b/scripts/Windows/run.ps1 index 0ab8fb35..25f60bc2 100644 --- a/scripts/Windows/run.ps1 +++ b/scripts/Windows/run.ps1 @@ -49,43 +49,20 @@ foreach ($prop in $config.services.PSObject.Properties) Write-Host " • Found image: $image" } -$Spinner = @('|', '/', '-', '\') -$Colors = @('White', 'Gray') -$Delay = 100 +Write-Host "Spinning services up..." -ForegroundColor Magenta +docker compose up --no-build --detach --wait +Write-Host "Services are up and healthy!" -ForegroundColor Green -function Invoke-Spinner -{ - [CmdletBinding()] - param( - [Parameter(Mandatory)][string]$Arguments, - [Parameter(Mandatory)][string]$Message - ) - Write-Host -NoNewLine -ForegroundColor Cyan ("{0}… " -f $Message) - $si = New-Object System.Diagnostics.ProcessStartInfo - $si.FileName = "docker" - $si.Arguments = "$Arguments --ansi never" - $si.RedirectStandardOutput = $true - $si.RedirectStandardError = $true - $si.UseShellExecute = $false - $p = [System.Diagnostics.Process]::Start($si) - $i = 0 - while (-not $p.HasExited) - { - $frame = $Spinner[$i % $Spinner.Length] - $col = $Colors[$i % $Colors.Length] - Write-Host -NoNewLine -ForegroundColor $col $frame - Start-Sleep -Milliseconds $Delay - Write-Host -NoNewLine "`b" - $i++ - } - $p.WaitForExit() - Write-Host -ForegroundColor Green "✔" -} +Write-Host "Applying migrations..." -ForegroundColor Magenta +docker compose exec app poetry run python manage.py migrate --no-input +Write-Host "Migrations applied successfully!" -ForegroundColor Green -Invoke-Spinner -Arguments "compose up --no-build --detach --wait" -Message "Starting services" -Invoke-Spinner -Arguments "compose exec app poetry run python manage.py migrate --no-input" -Message "Applying migrations" -Invoke-Spinner -Arguments "compose exec app poetry run python manage.py collectstatic --no-input" -Message "Collecting static files" -Invoke-Spinner -Arguments "compose exec app poetry run python manage.py set_default_caches" -Message "Setting default caches" -Invoke-Spinner -Arguments "compose exec app poetry run python manage.py set_default_caches" -Message "Cleaning up" +Write-Host "Collecting static files..." -ForegroundColor Magenta +docker compose exec app poetry run python manage.py collectstatic --no-input +Write-Host "Static files collected successfully!" -ForegroundColor Green -Write-Host "All done! eVibes is up and running." -ForegroundColor Green +Write-Host "Setting default caches..." -ForegroundColor Magenta +docker compose exec app poetry run python manage.py set_default_caches +Write-Host "Default caches set successfully!" -ForegroundColor Green + +Write-Host "All done! eVibes is up and running!" -ForegroundColor Cyan diff --git a/scripts/Windows/uninstall.ps1 b/scripts/Windows/uninstall.ps1 index 3bf410df..257108ae 100644 --- a/scripts/Windows/uninstall.ps1 +++ b/scripts/Windows/uninstall.ps1 @@ -25,43 +25,18 @@ $art -split "`r?`n" | ForEach-Object { Write-Host "`n by WISELESS TEAM`n" -ForegroundColor Gray -$Spinner = @('|', '/', '-', '\') -$Colors = @('White', 'Gray') -$Delay = 100 +Write-Host "Shutting down..." -ForegroundColor Magenta +docker compose down +Write-Host "Services were shut down successfully!" -ForegroundColor Green -function Invoke-Spinner -{ - [CmdletBinding()] - param( - [Parameter(Mandatory)][string]$Arguments, - [Parameter(Mandatory)][string]$Message - ) - Write-Host -NoNewLine -ForegroundColor Cyan ("{0}… " -f $Message) - $si = New-Object System.Diagnostics.ProcessStartInfo - $si.FileName = "docker" - $si.Arguments = "$Arguments --ansi never" - $si.RedirectStandardOutput = $true - $si.RedirectStandardError = $true - $si.UseShellExecute = $false - $p = [System.Diagnostics.Process]::Start($si) - $i = 0 - while (-not $p.HasExited) - { - $frame = $Spinner[$i % $Spinner.Length] - $col = $Colors[$i % $Colors.Length] - Write-Host -NoNewLine -ForegroundColor $col $frame - Start-Sleep -Milliseconds $Delay - Write-Host -NoNewline "`b" - $i++ - } - $p.WaitForExit() - Write-Host -ForegroundColor Green "✔" -} +Write-Host "Cleaning up unused Docker data..." -ForegroundColor Magenta +docker system prune -a -f --volumes +Write-Host "Unused Docker data cleaned successfully!" -ForegroundColor Green -Invoke-Spinner -Arguments "compose down" -Message "Killing services" -Invoke-Spinner -Arguments "system prune -f" -Message "Cleaning up Docker data" -Write-Host -ForegroundColor Cyan "Removing related files..." +Write-Host "Removing related files..." -ForegroundColor Magenta Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ./services_data Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ./media Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ./static -Write-Host -ForegroundColor Red "Bye-bye, hope you return later!" +Write-Host "Related files removed successfully!" -ForegroundColor Magenta + +Write-Host "Bye-bye, hope you return later!" -ForegroundColor Red \ No newline at end of file