rss feed and download stats

This commit is contained in:
2026-07-28 10:42:39 +02:00
parent 746143decd
commit 4afa440b12
20 changed files with 186 additions and 14 deletions
@@ -0,0 +1,11 @@
class Api::RssController < ApiController
def blog
xml = RssService.new.blog_feed
render xml: xml, content_type: "application/rss+xml"
end
def releases
xml = RssService.new.releases_feed
render xml: xml, content_type: "application/rss+xml"
end
end
@@ -0,0 +1,23 @@
class DownloadsController < ApplicationController
skip_forgery_protection
def show
path = params[:path]
return head :bad_request if path.blank?
full_path = DownloadService.new.call(
path: path,
ip: request.remote_ip,
user_agent: request.user_agent,
referer: request.referer
)
if full_path
ext = File.extname(full_path.to_s).delete_prefix(".")
mime = Mime::Type.lookup_by_extension(ext) || "application/octet-stream"
send_file full_path, disposition: "attachment", type: mime
else
head :not_found
end
end
end
+5
View File
@@ -0,0 +1,5 @@
class Download < ApplicationRecord
belongs_to :release, optional: true
validates :file_path, presence: true
end
+1
View File
@@ -2,6 +2,7 @@ class Release < ApplicationRecord
self.table_name = "releases"
belongs_to :software
has_many :downloads
default_scope { where(deleted_at: nil) }
+1
View File
@@ -4,6 +4,7 @@ class Software < ApplicationRecord
has_many :software_images, foreign_key: :software_id, dependent: :destroy
has_many :images, through: :software_images
has_many :releases, foreign_key: :software_id
has_many :downloads, through: :releases
has_many :external_links, foreign_key: :software_id
accepts_nested_attributes_for :software_images, allow_destroy: true
@@ -14,4 +14,5 @@ class ReleaseSerializer < Blueprinter::Base
field(:sourcePath) { |r| r.source_path.blank? ? "" : r.source_path.gsub(FILE_PATH_FROM, FILE_PATH_TO) }
field(:htmlFolderPath) { |r| r.html_folder_path.blank? ? "" : r.html_folder_path.gsub(FILE_PATH_FROM, FILE_PATH_TO) }
field(:docsFolderPath) { |r| r.docs_folder_path.blank? ? "" : r.docs_folder_path.gsub(FILE_PATH_FROM, FILE_PATH_TO) }
field(:downloadCount) { |r| r.downloads.size }
end
@@ -3,4 +3,5 @@ class SoftwareDetailSerializer < Blueprinter::Base
field(:releases) { |_, opts| ReleaseSerializer.render_as_hash(opts[:releases]) }
field(:latestRelease) { |_, opts| opts[:latest] ? ReleaseSerializer.render_as_hash(opts[:latest]) : nil }
field(:webPlayableRelease) { |_, opts| opts[:web_playable] ? ReleaseSerializer.render_as_hash(opts[:web_playable]) : nil }
field(:totalDownloads) { |sw, _| Download.where(release: sw.releases).count }
end
+21
View File
@@ -0,0 +1,21 @@
class DownloadService
BASE_PATH = Pathname.new(ENV.fetch("FILE_CONTAINER_PATH", "/softwares")).realpath
def call(path:, ip:, user_agent:, referer:)
full_path = BASE_PATH.join(path.to_s)
return nil unless File.file?(full_path)
release = Release.find_by("cartridge_path LIKE ? OR source_path LIKE ?",
"%#{path}%", "%#{path}%")
Download.create!(
file_path: path,
release: release,
ip_address: ip,
user_agent: user_agent&.truncate(500),
referer: referer&.truncate(500)
)
full_path
end
end
+51
View File
@@ -0,0 +1,51 @@
require "rss"
class RssService
SITE_URL = ENV.fetch("SITE_URL", "https://teletypegames.hu").freeze
def blog_feed
pages = WikiService.new.pages(tag: "blog", limit: 30)
items = pages.fetch("pages", [])
RSS::Maker.make("2.0") do |maker|
maker.channel.title = "Teletype Games Blog"
maker.channel.link = "#{SITE_URL}/blog"
maker.channel.description = "Latest blog posts from Teletype Games"
maker.channel.language = "hu"
items.each do |page|
maker.items.new_item do |item|
item.title = page["title"]
item.link = "#{SITE_URL}/blog/#{page["route"]}"
item.description = page["description"].to_s
item.pubDate = Time.parse(page["createdAt"]) rescue Time.current
end
end
end.to_s
end
def releases_feed
releases = Release.includes(:software)
.order(created_at: :desc)
.limit(50)
RSS::Maker.make("2.0") do |maker|
maker.channel.title = "Teletype Games Releases"
maker.channel.link = "#{SITE_URL}/catalog"
maker.channel.description = "Latest game releases from Teletype Games"
maker.channel.language = "hu"
releases.each do |release|
sw = release.software
next unless sw
maker.items.new_item do |item|
item.title = "#{sw.title} v#{release.version}"
item.link = "#{SITE_URL}/catalog/#{sw.name}"
item.description = "#{sw.title} #{release.version} released #{sw.desc}"
item.pubDate = release.created_at
end
end
end.to_s
end
end
@@ -8,7 +8,7 @@ class SoftwareHighlightedService
.first
return nil unless software
releases = Release.where(software_id: software.id).to_a
releases = Release.includes(:downloads).where(software_id: software.id).to_a
build_response(software, releases)
end
end
+1 -1
View File
@@ -2,7 +2,7 @@ class SoftwareService
include SoftwareResponseBuilder
def index
softwares = Software.includes(:releases, :external_links, :software_images).all
softwares = Software.includes(releases: :downloads).includes(:external_links, :software_images).all
{ softwares: softwares.map { |sw| build_response(sw, sw.releases.to_a) } }
end
end
+3
View File
@@ -9,8 +9,11 @@ Rails.application.routes.draw do
get "members", to: "members#index"
get "image/:id", to: "images#show"
get "wiki/pages", to: "wiki#index"
get "rss/blog", to: "rss#blog"
get "rss/releases", to: "rss#releases"
end
get "download", to: "downloads#show"
get "update", to: "update#update"
get "file/*path", to: "files#show", format: false
end
@@ -0,0 +1,19 @@
class CreateDownloads < ActiveRecord::Migration[8.1]
def change
create_table :downloads, id: { type: :bigint, unsigned: true },
charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci" do |t|
t.string :file_path, null: false
t.bigint :release_id, unsigned: true
t.string :ip_address
t.string :user_agent, limit: 500
t.string :referer, limit: 500
t.datetime :created_at, precision: 3
t.datetime :updated_at, precision: 3
t.index :file_path
t.index :release_id
end
add_foreign_key :downloads, :releases, name: "fk_downloads_release" if table_exists?(:releases)
end
end