From 724e5bc748abe3e4f2952b2623604a5df18a9042 Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Tue, 4 Aug 2026 15:45:13 +0200 Subject: [PATCH] refact round --- .../controllers/api/downloads_controller.rb | 2 +- .../app/controllers/api/wiki_controller.rb | 2 +- apps/api/app/controllers/update_controller.rb | 14 +++++++---- apps/api/app/models/admin_user.rb | 4 +++- apps/api/app/models/download.rb | 4 +++- apps/api/app/models/image.rb | 4 +++- apps/api/app/models/member.rb | 4 +++- .../serializers/concerns/timestamp_fields.rb | 4 ++++ apps/api/app/serializers/event_serializer.rb | 4 ++-- .../serializers/external_link_serializer.rb | 11 ++++----- apps/api/app/serializers/member_serializer.rb | 6 +++-- .../api/app/serializers/release_serializer.rb | 12 +++++----- .../app/serializers/software_serializer.rb | 11 ++++----- apps/api/app/services/builds_service.rb | 2 +- apps/api/app/services/download_service.rb | 2 +- apps/api/app/services/update_service.rb | 4 +--- apps/api/app/services/wiki_service.rb | 2 +- ...000002_add_deleted_at_to_missing_models.rb | 8 +++++++ apps/frontend/src/api/software.api.ts | 2 +- .../src/lib/interfaces/member.interface.ts | 6 ++--- .../src/lib/interfaces/software.interface.ts | 2 +- apps/frontend/src/lib/softwareUtils.ts | 4 ++-- apps/frontend/src/page/blog/BlogIndexPage.vue | 2 +- apps/frontend/src/page/blog/BlogShowPage.vue | 7 +++--- .../src/page/builds/BuildsIndexPage.vue | 3 ++- .../src/page/catalog/CatalogIndexPage.vue | 9 ++++--- .../src/page/catalog/CatalogShowPage.vue | 2 +- apps/frontend/src/page/code/CodeIndexPage.vue | 2 +- apps/frontend/src/page/home/HomePage.vue | 10 ++++---- .../src/page/howtos/HowtosIndexPage.vue | 7 ++++-- apps/frontend/src/page/team/TeamIndexPage.vue | 9 ++++--- .../stores/__tests__/software.store.test.ts | 8 +++---- apps/frontend/src/stores/blog.store.ts | 24 ++++++------------- apps/frontend/src/stores/git.store.ts | 4 ++-- apps/frontend/src/stores/howtos.store.ts | 4 ++-- apps/frontend/src/stores/member.store.ts | 4 ++-- apps/frontend/src/stores/software.store.ts | 11 ++++----- apps/frontend/src/stores/youtube.store.ts | 21 +++++++--------- 38 files changed, 129 insertions(+), 112 deletions(-) create mode 100644 apps/api/app/serializers/concerns/timestamp_fields.rb create mode 100644 apps/api/db/migrate/20260804000002_add_deleted_at_to_missing_models.rb diff --git a/apps/api/app/controllers/api/downloads_controller.rb b/apps/api/app/controllers/api/downloads_controller.rb index e000aab..73464cc 100644 --- a/apps/api/app/controllers/api/downloads_controller.rb +++ b/apps/api/app/controllers/api/downloads_controller.rb @@ -13,7 +13,7 @@ class Api::DownloadsController < ApiController path = params[:path] return render(json: { error: "Path is required" }, status: :bad_request) if path.blank? - full_path = DownloadService.new.call( + full_path = DownloadService.new.create( path: path, ip: request.remote_ip, user_agent: request.user_agent, diff --git a/apps/api/app/controllers/api/wiki_controller.rb b/apps/api/app/controllers/api/wiki_controller.rb index d14a403..0e6d865 100644 --- a/apps/api/app/controllers/api/wiki_controller.rb +++ b/apps/api/app/controllers/api/wiki_controller.rb @@ -27,7 +27,7 @@ class Api::WikiController < ApiController end # GET /api/wiki/pages?tag=blog|howto[&limit=30][&body=1] def index - render json: WikiService.new.pages( + render json: WikiService.new.index( tag: params[:tag], limit: params[:limit], body: params[:body] diff --git a/apps/api/app/controllers/update_controller.rb b/apps/api/app/controllers/update_controller.rb index 94c1efd..b6afbb5 100644 --- a/apps/api/app/controllers/update_controller.rb +++ b/apps/api/app/controllers/update_controller.rb @@ -4,6 +4,15 @@ class UpdateController < ApiController formats [ "text" ] end + rescue_from ArgumentError do |e| + render plain: e.message, status: :bad_request + end + + rescue_from StandardError do |e| + Rails.logger.error("[UpdateController] #{e.class}: #{e.message}\n#{e.backtrace.first(5).join("\n")}") + render plain: "Internal server error", status: :internal_server_error + end + api :GET, "/update", "Update software version in database" param :secret, String, required: true, desc: "Authorization secret" param :platform, String, required: false, desc: "Platform (tic80, love, ebitengine, c64, godot, bevy, phaser)" @@ -25,11 +34,6 @@ class UpdateController < ApiController UpdateService.new.update(input) render plain: "Updated" - rescue ArgumentError => e - render plain: e.message, status: :bad_request - rescue => e - Rails.logger.error("[UpdateController] #{e.class}: #{e.message}\n#{e.backtrace.first(5).join("\n")}") - render plain: "Internal server error", status: :internal_server_error end private diff --git a/apps/api/app/models/admin_user.rb b/apps/api/app/models/admin_user.rb index 5f38551..ae49dcb 100644 --- a/apps/api/app/models/admin_user.rb +++ b/apps/api/app/models/admin_user.rb @@ -4,8 +4,10 @@ class AdminUser < ApplicationRecord belongs_to :member, optional: true + default_scope { where(deleted_at: nil) } + def self.ransackable_attributes(auth_object = nil) - %w[created_at email id member_id updated_at] + %w[created_at deleted_at email id member_id updated_at] end def self.ransackable_associations(auth_object = nil) diff --git a/apps/api/app/models/download.rb b/apps/api/app/models/download.rb index 36f1bbe..e8e7659 100644 --- a/apps/api/app/models/download.rb +++ b/apps/api/app/models/download.rb @@ -3,8 +3,10 @@ class Download < ApplicationRecord validates :file_path, presence: true + default_scope { where(deleted_at: nil) } + def self.ransackable_attributes(auth_object = nil) - %w[created_at file_path id ip_address referer release_id updated_at user_agent] + %w[created_at deleted_at file_path id ip_address referer release_id updated_at user_agent] end def self.ransackable_associations(auth_object = nil) diff --git a/apps/api/app/models/image.rb b/apps/api/app/models/image.rb index 1bdaa46..dc295b7 100644 --- a/apps/api/app/models/image.rb +++ b/apps/api/app/models/image.rb @@ -3,12 +3,14 @@ class Image < ApplicationRecord has_many :software_images, dependent: :restrict_with_error + default_scope { where(deleted_at: nil) } + attr_accessor :file_upload before_save :process_upload, if: -> { file_upload.present? } def self.ransackable_attributes(auth_object = nil) - %w[content_type created_at filename id original_filename updated_at] + %w[content_type created_at deleted_at filename id original_filename updated_at] end def file_path diff --git a/apps/api/app/models/member.rb b/apps/api/app/models/member.rb index 953d2ad..11026e3 100644 --- a/apps/api/app/models/member.rb +++ b/apps/api/app/models/member.rb @@ -4,8 +4,10 @@ class Member < ApplicationRecord validates :nick, presence: true, uniqueness: true + default_scope { where(deleted_at: nil) } + def self.ransackable_attributes(auth_object = nil) - %w[avatar_filename created_at id image_id motto nick real_nick updated_at] + %w[avatar_filename created_at deleted_at id image_id motto nick real_nick updated_at] end def self.ransackable_associations(auth_object = nil) diff --git a/apps/api/app/serializers/concerns/timestamp_fields.rb b/apps/api/app/serializers/concerns/timestamp_fields.rb new file mode 100644 index 0000000..e675a6f --- /dev/null +++ b/apps/api/app/serializers/concerns/timestamp_fields.rb @@ -0,0 +1,4 @@ +module TimestampFields + GO_ZERO_TIME = "0001-01-01T00:00:00Z" + TS_FORMAT = "%Y-%m-%dT%H:%M:%S.%3NZ" +end diff --git a/apps/api/app/serializers/event_serializer.rb b/apps/api/app/serializers/event_serializer.rb index 082ea8e..2a1c986 100644 --- a/apps/api/app/serializers/event_serializer.rb +++ b/apps/api/app/serializers/event_serializer.rb @@ -1,6 +1,6 @@ class EventSerializer < Blueprinter::Base - DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" + include TimestampFields field :name - field(:date) { |event| event.date.utc.strftime(DATE_FORMAT) } + field(:date) { |event| event.date.utc.strftime(TS_FORMAT) } end diff --git a/apps/api/app/serializers/external_link_serializer.rb b/apps/api/app/serializers/external_link_serializer.rb index 242cf47..a7e5f8a 100644 --- a/apps/api/app/serializers/external_link_serializer.rb +++ b/apps/api/app/serializers/external_link_serializer.rb @@ -1,11 +1,10 @@ class ExternalLinkSerializer < Blueprinter::Base - GO_ZERO_TIME = "0001-01-01T00:00:00Z" - TS_FORMAT = "%Y-%m-%dT%H:%M:%S.%3NZ" + include TimestampFields - field(:ID) { |el| el.id } - field(:CreatedAt) { |el| el.created_at&.utc&.strftime(TS_FORMAT) || GO_ZERO_TIME } - field(:UpdatedAt) { |el| el.updated_at&.utc&.strftime(TS_FORMAT) || GO_ZERO_TIME } - field(:DeletedAt) { |el| el.deleted_at&.utc&.strftime(TS_FORMAT) } + field(:id) { |el| el.id } + field(:createdAt) { |el| el.created_at&.utc&.strftime(TS_FORMAT) || GO_ZERO_TIME } + field(:updatedAt) { |el| el.updated_at&.utc&.strftime(TS_FORMAT) || GO_ZERO_TIME } + field(:deletedAt) { |el| el.deleted_at&.utc&.strftime(TS_FORMAT) } field(:softwareId) { |el| el.software_id } field :label field :url diff --git a/apps/api/app/serializers/member_serializer.rb b/apps/api/app/serializers/member_serializer.rb index 8260c2a..ee8da59 100644 --- a/apps/api/app/serializers/member_serializer.rb +++ b/apps/api/app/serializers/member_serializer.rb @@ -1,5 +1,7 @@ class MemberSerializer < Blueprinter::Base - fields :nick, :real_nick, :motto, :avatar_filename + fields :nick, :motto - field(:image_url) { |member| member.image_id ? "/api/image/#{member.image_id}" : nil } + field(:realNick) { |m| m.real_nick } + field(:avatarFilename) { |m| m.avatar_filename } + field(:imageUrl) { |m| m.image_id ? "/api/image/#{m.image_id}" : nil } end diff --git a/apps/api/app/serializers/release_serializer.rb b/apps/api/app/serializers/release_serializer.rb index 6b0183f..00ec0c8 100644 --- a/apps/api/app/serializers/release_serializer.rb +++ b/apps/api/app/serializers/release_serializer.rb @@ -1,6 +1,6 @@ class ReleaseSerializer < Blueprinter::Base - GO_ZERO_TIME = "0001-01-01T00:00:00Z" - TS_FORMAT = "%Y-%m-%dT%H:%M:%S.%3NZ" + include TimestampFields + FILE_PATH_FROM = "/softwares/" FILE_PATH_TO = "/file/" @@ -13,10 +13,10 @@ class ReleaseSerializer < Blueprinter::Base asset ? asset.path.gsub(FILE_PATH_FROM, FILE_PATH_TO) : "" end - field(:ID) { |r| r.id } - field(:CreatedAt) { |r| r.created_at&.utc&.strftime(TS_FORMAT) || GO_ZERO_TIME } - field(:UpdatedAt) { |r| r.updated_at&.utc&.strftime(TS_FORMAT) || GO_ZERO_TIME } - field(:DeletedAt) { |r| r.deleted_at&.utc&.strftime(TS_FORMAT) } + field(:id) { |r| r.id } + field(:createdAt) { |r| r.created_at&.utc&.strftime(TS_FORMAT) || GO_ZERO_TIME } + field(:updatedAt) { |r| r.updated_at&.utc&.strftime(TS_FORMAT) || GO_ZERO_TIME } + field(:deletedAt) { |r| r.deleted_at&.utc&.strftime(TS_FORMAT) } field(:softwareId) { |r| r.software_id } field :version field(:cartridgePath) { |r| asset_path(r, "cartridge") } diff --git a/apps/api/app/serializers/software_serializer.rb b/apps/api/app/serializers/software_serializer.rb index 7719d4a..dad76ef 100644 --- a/apps/api/app/serializers/software_serializer.rb +++ b/apps/api/app/serializers/software_serializer.rb @@ -1,11 +1,10 @@ class SoftwareSerializer < Blueprinter::Base - GO_ZERO_TIME = "0001-01-01T00:00:00Z" - TS_FORMAT = "%Y-%m-%dT%H:%M:%S.%3NZ" + include TimestampFields - field(:ID) { |sw| sw.id } - field(:CreatedAt) { |sw| sw.created_at&.utc&.strftime(TS_FORMAT) || GO_ZERO_TIME } - field(:UpdatedAt) { |sw| sw.updated_at&.utc&.strftime(TS_FORMAT) || GO_ZERO_TIME } - field(:DeletedAt) { |sw| sw.deleted_at&.utc&.strftime(TS_FORMAT) } + field(:id) { |sw| sw.id } + field(:createdAt) { |sw| sw.created_at&.utc&.strftime(TS_FORMAT) || GO_ZERO_TIME } + field(:updatedAt) { |sw| sw.updated_at&.utc&.strftime(TS_FORMAT) || GO_ZERO_TIME } + field(:deletedAt) { |sw| sw.deleted_at&.utc&.strftime(TS_FORMAT) } field :name field :title field :author diff --git a/apps/api/app/services/builds_service.rb b/apps/api/app/services/builds_service.rb index e558afc..f16d548 100644 --- a/apps/api/app/services/builds_service.rb +++ b/apps/api/app/services/builds_service.rb @@ -1,6 +1,6 @@ class BuildsService def index - platforms = UpdateService::SUPPORTED_PLATFORMS.each_with_object({}) do |platform, hash| + platforms = PlatformLink::SUPPORTED_PLATFORMS.each_with_object({}) do |platform, hash| service_class = "SoftwareUpdater::#{platform.camelize}Service".constantize hash[platform] = { label: service_class.label, diff --git a/apps/api/app/services/download_service.rb b/apps/api/app/services/download_service.rb index d9f6f1e..31296c5 100644 --- a/apps/api/app/services/download_service.rb +++ b/apps/api/app/services/download_service.rb @@ -2,7 +2,7 @@ class DownloadService CONTAINER_BASE = ENV.fetch("FILE_CONTAINER_PATH", "/softwares") BASE_PATH = Pathname.new(CONTAINER_BASE).realpath - def call(path:, ip:, user_agent:, referer:) + def create(path:, ip:, user_agent:, referer:) sanitized = path.to_s full_path = BASE_PATH.join(sanitized).realpath return nil unless full_path.to_s.start_with?(BASE_PATH.to_s) diff --git a/apps/api/app/services/update_service.rb b/apps/api/app/services/update_service.rb index 7814e3e..688b0e6 100644 --- a/apps/api/app/services/update_service.rb +++ b/apps/api/app/services/update_service.rb @@ -1,8 +1,6 @@ class UpdateService - SUPPORTED_PLATFORMS = %w[tic80 ebitengine love c64 godot bevy phaser].freeze - def update(input) - unless SUPPORTED_PLATFORMS.include?(input.platform) + unless PlatformLink::SUPPORTED_PLATFORMS.include?(input.platform) raise ArgumentError, "Unsupported platform: #{input.platform}" end diff --git a/apps/api/app/services/wiki_service.rb b/apps/api/app/services/wiki_service.rb index 172020a..e51d508 100644 --- a/apps/api/app/services/wiki_service.rb +++ b/apps/api/app/services/wiki_service.rb @@ -21,7 +21,7 @@ require "json" class WikiService GRAV_URL = ENV.fetch("WIKI_GRAV_URL", "http://localhost:8080").freeze - def pages(tag:, limit: nil, body: nil) + def index(tag:, limit: nil, body: nil) query = { tag: tag } query[:limit] = limit if limit.present? query[:body] = body if body.present? diff --git a/apps/api/db/migrate/20260804000002_add_deleted_at_to_missing_models.rb b/apps/api/db/migrate/20260804000002_add_deleted_at_to_missing_models.rb new file mode 100644 index 0000000..0234bd2 --- /dev/null +++ b/apps/api/db/migrate/20260804000002_add_deleted_at_to_missing_models.rb @@ -0,0 +1,8 @@ +class AddDeletedAtToMissingModels < ActiveRecord::Migration[8.0] + def change + %i[images members downloads admin_users].each do |table| + add_column table, :deleted_at, :datetime, precision: 3 + add_index table, :deleted_at, name: "idx_#{table}_deleted_at" + end + end +end diff --git a/apps/frontend/src/api/software.api.ts b/apps/frontend/src/api/software.api.ts index 9ed5303..a0c48e4 100644 --- a/apps/frontend/src/api/software.api.ts +++ b/apps/frontend/src/api/software.api.ts @@ -9,7 +9,7 @@ const index = async (): Promise => { const highlighted = async (): Promise => { const res = await fetch('/api/software/highlighted') - if (!res.ok) return null + if (!res.ok) throw new Error(`HTTP ${res.status}`) return res.json() } diff --git a/apps/frontend/src/lib/interfaces/member.interface.ts b/apps/frontend/src/lib/interfaces/member.interface.ts index 1f6bc79..1ecfbb7 100644 --- a/apps/frontend/src/lib/interfaces/member.interface.ts +++ b/apps/frontend/src/lib/interfaces/member.interface.ts @@ -1,7 +1,7 @@ export interface Member { nick: string - real_nick: string + realNick: string motto: string - avatar_filename: string - image_url?: string | null + avatarFilename: string + imageUrl?: string | null } diff --git a/apps/frontend/src/lib/interfaces/software.interface.ts b/apps/frontend/src/lib/interfaces/software.interface.ts index 5c337f3..716edf0 100644 --- a/apps/frontend/src/lib/interfaces/software.interface.ts +++ b/apps/frontend/src/lib/interfaces/software.interface.ts @@ -11,7 +11,7 @@ export interface Release { docsFolderPath?: string assets?: ReleaseAsset[] downloadCount?: number - UpdatedAt: string + updatedAt: string } export interface SoftwareImage { diff --git a/apps/frontend/src/lib/softwareUtils.ts b/apps/frontend/src/lib/softwareUtils.ts index 17f5b32..959b486 100644 --- a/apps/frontend/src/lib/softwareUtils.ts +++ b/apps/frontend/src/lib/softwareUtils.ts @@ -17,13 +17,13 @@ export function getDefaultImageUrl(sw: Software): string | null { export function filterStableReleases(releases: Release[]): Release[] { return releases .filter(r => !r.version.startsWith('dev-')) - .sort((a, b) => new Date(b.UpdatedAt).getTime() - new Date(a.UpdatedAt).getTime()) + .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()) } export function filterDevReleases(releases: Release[]): Release[] { return releases .filter(r => r.version.startsWith('dev-')) - .sort((a, b) => new Date(b.UpdatedAt).getTime() - new Date(a.UpdatedAt).getTime()) + .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()) } export function getLatestStable(releases: Release[]): Release | undefined { diff --git a/apps/frontend/src/page/blog/BlogIndexPage.vue b/apps/frontend/src/page/blog/BlogIndexPage.vue index e5b381f..d98d3c0 100644 --- a/apps/frontend/src/page/blog/BlogIndexPage.vue +++ b/apps/frontend/src/page/blog/BlogIndexPage.vue @@ -86,7 +86,7 @@ const store = useBlogStore() const { pages: blogPages, loading, error } = storeToRefs(store) const { isNew, getPermalink, getCleanPreview } = store -onMounted(() => store.fetchPages()) +onMounted(() => store.fetch())