Skip to content

Commit 6c21678

Browse files
Add checks for urls (#243)
1 parent 08a0ae1 commit 6c21678

File tree

2 files changed

+116
-1
lines changed

2 files changed

+116
-1
lines changed

app/controllers/projects_controller.rb

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def create
3030
authorize @project
3131

3232
validate_hackatime_projects
33+
validate_urls
3334

3435
if @project.errors.empty? && @project.save
3536
# Create membership for the current user as owner
@@ -52,7 +53,10 @@ def edit
5253
def update
5354
authorize @project
5455

55-
if @project.update(project_params)
56+
@project.assign_attributes(project_params)
57+
validate_urls
58+
59+
if @project.errors.empty? && @project.save
5660
flash[:notice] = "Project updated successfully"
5761
redirect_to @project
5862
else
@@ -101,6 +105,93 @@ def validate_hackatime_projects
101105
@project.errors.add(:base, "The following Hackatime projects are already linked: #{already_linked.pluck(:name).join(', ')}")
102106
end
103107

108+
def validate_urls
109+
if @project.demo_url.blank? && @project.repo_url.blank? && @project.readme_url.blank?
110+
return
111+
end
112+
113+
114+
if @project.demo_url.present? && @project.repo_url.present?
115+
if @project.demo_url == @project.repo_url || @project.demo_url == @project.readme_url
116+
@project.errors.add(:base, "Demo URL and Repository URL cannot be the same")
117+
end
118+
end
119+
120+
validate_url_not_dead(:demo_url, "Demo URL") if @project.demo_url.present? && @project.errors.empty?
121+
122+
validate_url_not_dead(:repo_url, "Repository URL") if @project.repo_url.present? && @project.errors.empty?
123+
validate_url_not_dead(:readme_url, "Readme URL") if @project.readme_url.present? && @project.errors.empty?
124+
end
125+
126+
def validate_url_not_dead(attribute, name)
127+
require "uri"
128+
require "faraday"
129+
130+
return unless @project.send(attribute).present?
131+
132+
uri = URI.parse(@project.send(attribute))
133+
conn = Faraday.new(
134+
url: uri.to_s,
135+
headers: { "User-Agent" => "Flavortown project validtor (https://flavortown.hackclub.com/)" }
136+
)
137+
response = conn.get() do |req|
138+
req.options.timeout = 5
139+
req.options.open_timeout = 5
140+
end
141+
142+
unless response.status == 200
143+
@project.errors.add(attribute, "Your #{name} needs to return a 200 status. I got #{response.status}, is your code/website set to public!?!?")
144+
end
145+
146+
147+
# Copy pasted from https://github.com/hackclub/summer-of-making/blob/29e572dd6df70627d37f3718a6ebd4bafb07f4c7/app/controllers/projects_controller.rb#L275
148+
if attribute != :demo_url
149+
repo_patterns = [
150+
%r{/blob/}, %r{/tree/}, %r{/src/}, %r{/raw/}, %r{/commits/},
151+
%r{/pull/}, %r{/issues/}, %r{/compare/}, %r{/releases/},
152+
/\.git$/, %r{/commit/}, %r{/branch/}, %r{/blame/},
153+
154+
%r{/projects/}, %r{/repositories/}, %r{/gitea/}, %r{/cgit/},
155+
%r{/gitweb/}, %r{/gogs/}, %r{/git/}, %r{/scm/},
156+
157+
/\.(md|py|js|ts|jsx|tsx|html|css|scss|php|rb|go|rs|java|cpp|c|h|cs|swift)$/
158+
]
159+
160+
# Known code hosting platforms (not required, but used for heuristic)
161+
known_platforms = [
162+
"github", "gitlab", "bitbucket", "dev.azure", "sourceforge",
163+
"codeberg", "sr.ht", "replit", "vercel", "netlify", "glitch",
164+
"hackclub", "gitea", "git", "repo", "code"
165+
]
166+
167+
path = uri.path.downcase
168+
host = uri.host.downcase
169+
170+
is_valid_repo_url = false
171+
172+
if repo_patterns.any? { |pattern| path.match?(pattern) }
173+
is_valid_repo_url = true
174+
elsif attribute == :readme_url && (host.include?("raw.githubusercontent") || path.include?("/readme") || path.end_with?(".md") || path.end_with?("readme.txt"))
175+
is_valid_repo_url = true
176+
elsif known_platforms.any? { |platform| host.include?(platform) }
177+
is_valid_repo_url = path.split("/").size > 2
178+
elsif path.split("/").size > 1 && path.exclude?("wp-") && path.exclude?("blog")
179+
is_valid_repo_url = true
180+
end
181+
182+
unless is_valid_repo_url
183+
@project.errors.add(attribute, "#{name} does not appear to be a valid repository or project URL")
184+
end
185+
end
186+
187+
rescue URI::InvalidURIError
188+
@project.errors.add(attribute, "#{name} is not a valid URL")
189+
rescue Faraday::ConnectionFailed => e
190+
@project.errors.add(attribute, "Please make sure the url is valid and reachable: #{e.message}")
191+
rescue StandardError => e
192+
@project.errors.add(attribute, "#{name} could not be verified (idk why, pls let a admin know if this is happning alot and your sure that the url is valid): #{e.message}")
193+
end
194+
104195
def link_hackatime_projects
105196
return if hackatime_project_ids.empty?
106197

app/javascript/controllers/project_form_controller.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export default class extends Controller {
2525

2626
this.updateSubmitState(); // submit button
2727

28+
this.restorReadmeWhenThereIsAError();
29+
2830
if (
2931
this.hasRepoUrlTarget &&
3032
this.hasReadmeUrlTarget &&
@@ -238,6 +240,28 @@ export default class extends Controller {
238240
}
239241
}
240242

243+
restorReadmeWhenThereIsAError() {
244+
if (!this.hasReadmeContainerTarget || !this.hasReadmeUrlTarget) return;
245+
const value = (this.readmeUrlTarget.value || "").trim();
246+
if (!value) return;
247+
248+
this.readmeContainerTarget.hidden = false;
249+
250+
if (this.readmeUrlTarget.dataset.autofilled === "true") {
251+
this.readmeUrlTarget.readOnly = true;
252+
const control = this.readmeUrlTarget.closest(".input__control");
253+
if (control) control.classList.add("input__control--locked");
254+
this.readmeUrlTarget.title = "Autodetected from repository (locked)";
255+
this.userEditedReadme = false;
256+
} else {
257+
this.readmeUrlTarget.readOnly = false;
258+
const control = this.readmeUrlTarget.closest(".input__control");
259+
if (control) control.classList.remove("input__control--locked");
260+
this.readmeUrlTarget.removeAttribute("title");
261+
this.userEditedReadme = true;
262+
}
263+
}
264+
241265
// util
242266
debounce(fn, wait) {
243267
let t;

0 commit comments

Comments
 (0)