Sync in and display repo metadata

This commit is contained in:
Zach Latta
2025-05-30 09:57:20 -04:00
parent b8c7d05454
commit efd19568b7
22 changed files with 558 additions and 25 deletions

View File

@@ -11,6 +11,9 @@
padding: 1rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
transition: transform 0.2s ease;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.project-duration-card:hover {
@@ -21,8 +24,21 @@
.project-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 0.5rem;
}
.project-name-section {
display: flex;
flex-direction: column;
gap: 0.25rem;
flex: 1;
}
.project-actions {
display: flex;
gap: 0.5rem;
align-items: center;
margin-bottom: 0.5rem;
}
.project-header strong {
@@ -33,6 +49,34 @@
.project-time {
font-size: 0.875rem;
color: var(--muted-color);
font-weight: 600;
}
.project-stars {
font-size: 0.75rem;
color: var(--muted-color);
}
.project-description {
font-size: 0.875rem;
color: var(--muted-color);
line-height: 1.4;
}
.project-languages {
font-size: 0.8rem;
color: var(--muted-color);
}
.languages-label {
font-weight: 600;
margin-right: 0.25rem;
}
.project-meta {
font-size: 0.75rem;
color: var(--muted-color);
margin-top: 0.25rem;
}
.project-progress-bar {

View File

@@ -95,7 +95,7 @@ class StaticPagesController < ApplicationController
def project_durations
return unless current_user
@project_repo_mappings = current_user.project_repo_mappings
@project_repo_mappings = current_user.project_repo_mappings.includes(:repository)
cache_key = "user_#{current_user.id}_project_durations_#{params[:interval]}"
cache_key += "_#{params[:from]}_#{params[:to]}" if params[:interval] == "custom"
@@ -104,9 +104,11 @@ class StaticPagesController < ApplicationController
project_times = heartbeats.group(:project).duration_seconds
project_labels = current_user.project_labels
project_times.map do |project, duration|
mapping = @project_repo_mappings.find { |p| p.project_name == project }
{
project: project_labels.find { |p| p.project_key == project }&.label || project || "Unknown",
repo_url: @project_repo_mappings.find { |p| p.project_name == project }&.repo_url,
repo_url: mapping&.repo_url,
repository: mapping&.repository,
duration: duration
}
end.filter { |p| p[:duration].positive? }.sort_by { |p| p[:duration] }.reverse

View File

@@ -10,9 +10,10 @@ class ProcessCommitJob < ApplicationJob
discard_on ActiveJob::DeserializationError # If User record is gone
def perform(user_id, commit_sha, commit_api_url, provider_string)
def perform(user_id, commit_sha, commit_api_url, provider_string, repository_id = nil)
provider_sym = provider_string.to_sym # Convert string back to symbol
user = User.find_by(id: user_id)
repository = repository_id ? Repository.find_by(id: repository_id) : nil
unless user
Rails.logger.warn "[ProcessCommitJob] User ##{user_id} not found. Skipping commit #{commit_sha}."
@@ -31,10 +32,10 @@ class ProcessCommitJob < ApplicationJob
case provider_sym
when :github
process_github_commit(user, commit_sha, commit_api_url)
process_github_commit(user, commit_sha, commit_api_url, repository)
# Add other providers like :gitlab later
# when :gitlab
# process_gitlab_commit(user, commit_sha, commit_api_url)
# process_gitlab_commit(user, commit_sha, commit_api_url, repository)
else
Rails.logger.error "[ProcessCommitJob] Unknown provider '#{provider_sym}' for commit #{commit_sha}."
end
@@ -42,7 +43,7 @@ class ProcessCommitJob < ApplicationJob
private
def process_github_commit(user, commit_sha, commit_api_url)
def process_github_commit(user, commit_sha, commit_api_url, repository)
unless user.github_access_token.present?
Rails.logger.warn "[ProcessCommitJob] User ##{user.id} missing GitHub token for commit #{commit_sha}. Skipping."
return
@@ -82,6 +83,7 @@ class ProcessCommitJob < ApplicationJob
Commit.create!(
sha: api_commit_sha,
user_id: user.id,
repository_id: repository&.id,
github_raw: commit_data_json,
created_at: commit_actual_created_at, # Manually set created_at
updated_at: Time.current # Let Rails handle updated_at, or set explicitly
@@ -95,7 +97,7 @@ class ProcessCommitJob < ApplicationJob
reset_time = Time.at(response.headers["X-RateLimit-Reset"].to_i)
delay_seconds = [ (reset_time - Time.current).ceil, 5 ].max # at least 5s delay
Rails.logger.warn "[ProcessCommitJob] GitHub API rate limit exceeded for User ##{user.id}. Retrying in #{delay_seconds}s. URL: #{commit_api_url}"
self.class.set(wait: delay_seconds.seconds).perform_later(user.id, commit_sha, commit_api_url, "github")
self.class.set(wait: delay_seconds.seconds).perform_later(user.id, commit_sha, commit_api_url, "github", repository&.id)
else
Rails.logger.error "[ProcessCommitJob] GitHub API forbidden (403) for User ##{user.id}. URL: #{commit_api_url}. Response: #{response.body.to_s.truncate(500)}"
end

View File

@@ -22,7 +22,11 @@ class PullRepoCommitsJob < ApplicationJob
return
end
Rails.logger.info "[PullRepoCommitsJob] Pulling commits for #{owner}/#{repo} for User ##{user.id}"
# Find the repository record
repo_url = "https://github.com/#{owner}/#{repo}"
repository = Repository.find_by(url: repo_url)
Rails.logger.info "[PullRepoCommitsJob] Pulling commits for #{owner}/#{repo} for User ##{user.id} (Repository: #{repository&.id})"
# Get commits from the last 3 days
since_date = 3.days.ago.iso8601
@@ -37,7 +41,7 @@ class PullRepoCommitsJob < ApplicationJob
if response.status.success?
commits_data = response.parse
process_commits(user, commits_data)
process_commits(user, commits_data, repository)
elsif response.status.code == 404
Rails.logger.warn "[PullRepoCommitsJob] Repository #{owner}/#{repo} not found (404) for User ##{user.id}."
elsif response.status.code == 403 # Forbidden, could be rate limit or permissions
@@ -65,7 +69,7 @@ class PullRepoCommitsJob < ApplicationJob
private
def process_commits(user, commits_data)
def process_commits(user, commits_data, repository)
return if commits_data.empty?
# Get existing commit SHAs to avoid duplicates
@@ -106,7 +110,8 @@ class PullRepoCommitsJob < ApplicationJob
user.id,
commit_sha,
commit_api_url,
"github"
"github",
repository&.id
)
enqueued_count += 1
else

View File

@@ -53,11 +53,23 @@ class ScanRepoEventsForCommitsJob < ApplicationJob
next
end
# Extract repository info from commit API URL
# Format: https://api.github.com/repos/owner/repo/commits/sha
repository_id = nil
if commit_api_url =~ %r{https://api\.github\.com/repos/([^/]+)/([^/]+)/commits/}
owner = $1
repo = $2
repo_url = "https://github.com/#{owner}/#{repo}"
repository = Repository.find_by(url: repo_url)
repository_id = repository&.id
end
potential_commits_buffer << {
sha: commit_sha,
api_url: commit_api_url,
user_id: user.id,
provider: event.provider.to_s
provider: event.provider.to_s,
repository_id: repository_id
}
end
@@ -101,7 +113,8 @@ class ScanRepoEventsForCommitsJob < ApplicationJob
commit_details[:user_id],
commit_details[:sha],
commit_details[:api_url],
commit_details[:provider]
commit_details[:provider],
commit_details[:repository_id]
)
enqueued_count += 1
end

View File

@@ -0,0 +1,36 @@
class SyncRepoMetadataJob < ApplicationJob
queue_as :default
retry_on HTTP::TimeoutError, HTTP::ConnectionError, wait: :exponentially_longer, attempts: 3
retry_on JSON::ParserError, wait: 10.seconds, attempts: 2
discard_on ArgumentError # Invalid repository URLs
def perform(repository_id)
repository = Repository.find_by(id: repository_id)
return unless repository
Rails.logger.info "[SyncRepoMetadataJob] Syncing metadata for #{repository.url}"
begin
# Use any user who has mapped to this repository for API access
user = repository.users.joins(:project_repo_mappings).first
return unless user
service = RepoHost::ServiceFactory.for_url(user, repository.url)
metadata = service.fetch_repo_metadata
if metadata
repository.update!(metadata)
Rails.logger.info "[SyncRepoMetadataJob] Updated metadata for #{repository.url}"
else
Rails.logger.warn "[SyncRepoMetadataJob] No metadata returned for #{repository.url}"
end
rescue ArgumentError => e
Rails.logger.error "[SyncRepoMetadataJob] #{e.message} for repository #{repository.id}"
raise # Discard job for unsupported hosts
rescue => e
Rails.logger.error "[SyncRepoMetadataJob] Unexpected error: #{e.message}"
raise # Retry for other errors
end
end
end

View File

@@ -0,0 +1,53 @@
class SyncStaleRepoMetadataJob < ApplicationJob
queue_as :default
def perform
Rails.logger.info "[SyncStaleRepoMetadataJob] Starting sync of stale repository metadata"
# Find all mappings where the repository has stale metadata or is missing metadata entirely
mappings_with_stale_repos = ProjectRepoMapping.includes(:repository, :user)
.joins(:repository)
.where("repositories.last_synced_at IS NULL OR repositories.last_synced_at < ?", 1.day.ago)
# Also find mappings where repository is nil (shouldn't happen, but just in case)
mappings_without_repos = ProjectRepoMapping.includes(:user)
.where(repository: nil)
all_stale_mappings = mappings_with_stale_repos.to_a + mappings_without_repos.to_a
Rails.logger.info "[SyncStaleRepoMetadataJob] Found #{all_stale_mappings.count} project mappings with stale or missing repository metadata"
# Group by repository to avoid duplicate API calls
repos_to_sync = {}
all_stale_mappings.each do |mapping|
if mapping.repository
repos_to_sync[mapping.repository.id] = mapping.repository
else
# Handle mappings without repository - recreate the repository
Rails.logger.warn "[SyncStaleRepoMetadataJob] Found mapping without repository: #{mapping.inspect}"
if mapping.repo_url.present?
begin
repo = Repository.find_or_create_by_url(mapping.repo_url)
mapping.update!(repository: repo)
repos_to_sync[repo.id] = repo
rescue => e
Rails.logger.error "[SyncStaleRepoMetadataJob] Failed to create repository for mapping #{mapping.id}: #{e.message}"
end
end
end
end
Rails.logger.info "[SyncStaleRepoMetadataJob] Enqueuing sync for #{repos_to_sync.count} unique repositories"
repos_to_sync.each_value do |repository|
# Only sync if the repository has at least one user (needed for API access)
next unless repository.users.exists?
Rails.logger.info "[SyncStaleRepoMetadataJob] Enqueuing sync for #{repository.url}"
SyncRepoMetadataJob.perform_later(repository.id)
end
Rails.logger.info "[SyncStaleRepoMetadataJob] Completed enqueuing sync jobs"
end
end

View File

@@ -4,6 +4,7 @@ class Commit < ApplicationRecord
self.primary_key = :sha
belongs_to :user
belongs_to :repository, optional: true
validates :sha, presence: true, uniqueness: true
validates :user_id, presence: true

View File

@@ -1,5 +1,6 @@
class ProjectRepoMapping < ApplicationRecord
belongs_to :user
belongs_to :repository, optional: true
has_paper_trail
@@ -20,7 +21,8 @@ class ProjectRepoMapping < ApplicationRecord
"<<LAST PROJECT>>"
]
after_create :schedule_commit_pull
after_create :create_repository_and_sync
after_update :sync_repository_if_url_changed
private
@@ -30,6 +32,24 @@ class ProjectRepoMapping < ApplicationRecord
end
end
def create_repository_and_sync
# Create or find repository record
repo = Repository.find_or_create_by_url(repo_url)
update_column(:repository_id, repo.id)
# Schedule commit pull and metadata sync
schedule_commit_pull
SyncRepoMetadataJob.perform_later(repo.id)
end
def sync_repository_if_url_changed
if saved_change_to_repo_url?
repo = Repository.find_or_create_by_url(repo_url)
update_column(:repository_id, repo.id)
SyncRepoMetadataJob.perform_later(repo.id)
end
end
def schedule_commit_pull
# Extract owner and repo name from the URL
# Example URL: https://github.com/owner/repo

44
app/models/repository.rb Normal file
View File

@@ -0,0 +1,44 @@
class Repository < ApplicationRecord
has_many :project_repo_mappings, dependent: :destroy
has_many :users, through: :project_repo_mappings
has_many :commits, dependent: :destroy
validates :url, presence: true, uniqueness: true
validates :host, presence: true
validates :owner, presence: true
validates :name, presence: true
# Check if metadata needs refreshing (older than 1 day)
def metadata_stale?
last_synced_at.nil? || last_synced_at < 1.day.ago
end
# Get formatted languages list
def formatted_languages
return nil if languages.blank?
languages.split(", ").first(3).join(", ") + (languages.split(", ").length > 3 ? "..." : "")
end
# Parse owner and repo from URL
def self.parse_url(url)
if url =~ %r{https?://([^/]+)/([^/]+)/([^/]+)/?$}
{
host: $1,
owner: $2,
name: $3
}
else
raise ArgumentError, "Invalid repository URL format: #{url}"
end
end
# Find or create repository from URL
def self.find_or_create_by_url(url)
parsed = parse_url(url)
find_or_create_by(url: url) do |repo|
repo.host = parsed[:host]
repo.owner = parsed[:owner]
repo.name = parsed[:name]
end
end
end

View File

@@ -0,0 +1,64 @@
module RepoHost
class BaseService
def initialize(user, repo_url)
@user = user
@repo_url = repo_url
@owner, @repo = parse_repo_url(repo_url)
end
def fetch_repo_metadata
raise NotImplementedError, "Subclasses must implement fetch_repo_metadata"
end
private
attr_reader :user, :repo_url, :owner, :repo
def parse_repo_url(url)
# Extract owner and repo from URL
# Example: https://github.com/owner/repo -> ["owner", "repo"]
# Example: https://gitlab.com/owner/repo -> ["owner", "repo"]
if url =~ %r{https?://[^/]+/([^/]+)/([^/]+)/?$}
[ $1, $2 ]
else
raise ArgumentError, "Invalid repository URL format: #{url}"
end
end
def api_headers
raise NotImplementedError, "Subclasses must implement api_headers"
end
def make_api_request(url)
response = HTTP.headers(api_headers)
.timeout(connect: 5, read: 10)
.get(url)
handle_response(response)
end
def handle_response(response)
case response.status.code
when 200
response.parse
when 403
handle_rate_limit(response)
when 404
Rails.logger.warn "[#{self.class.name}] Repository #{owner}/#{repo} not found (404)"
nil
else
Rails.logger.error "[#{self.class.name}] API error. Status: #{response.status}"
nil
end
end
def handle_rate_limit(response)
if response.headers["X-RateLimit-Remaining"]&.to_i == 0
reset_time = Time.at(response.headers["X-RateLimit-Reset"].to_i)
delay_seconds = [ (reset_time - Time.current).ceil, 5 ].max
Rails.logger.warn "[#{self.class.name}] Rate limit exceeded. Reset in #{delay_seconds}s"
end
nil
end
end
end

View File

@@ -0,0 +1,94 @@
require "http"
module RepoHost
class GithubService < BaseService
def fetch_repo_metadata
return nil unless user.github_access_token.present?
# Fetch basic repository info
repo_data = fetch_repository_info
return nil unless repo_data
# Fetch additional metadata
languages_data = fetch_languages
commits_data = fetch_recent_commits
commit_count = fetch_commit_count(repo_data["default_branch"])
{
stars: repo_data["stargazers_count"],
description: repo_data["description"],
language: repo_data["language"],
languages: languages_data&.keys&.join(", "),
homepage: repo_data["homepage"].presence,
commit_count: commit_count,
last_commit_at: commits_data&.first&.dig("commit", "committer", "date")&.then { |date| Time.parse(date) },
last_synced_at: Time.current
}
end
private
def api_headers
{
"Accept" => "application/vnd.github.v3+json",
"Authorization" => "Bearer #{user.github_access_token}",
"X-GitHub-Api-Version" => "2022-11-28"
}
end
def fetch_repository_info
url = "https://api.github.com/repos/#{owner}/#{repo}"
make_api_request(url)
end
def fetch_languages
url = "https://api.github.com/repos/#{owner}/#{repo}/languages"
make_api_request(url)
end
def fetch_recent_commits
# Get just the last few commits for metadata
url = "https://api.github.com/repos/#{owner}/#{repo}/commits?per_page=5"
make_api_request(url)
end
def fetch_commit_count(default_branch = nil)
# GitHub API doesn't provide commit count directly, so we need to use a workaround
# We'll get the commit count from the commits endpoint with minimal data
branch_param = default_branch ? "&sha=#{default_branch}" : ""
url = "https://api.github.com/repos/#{owner}/#{repo}/commits?per_page=1#{branch_param}"
response = HTTP.headers(api_headers)
.timeout(connect: 5, read: 10)
.get(url)
case response.status.code
when 200
# Extract commit count from Link header pagination
link_header = response.headers["Link"]
if link_header
# Look for the "last" page number in the Link header
# Format: <https://api.github.com/repos/owner/repo/commits?page=962&per_page=1>; rel="last"
if match = link_header.match(/.*page=(\d+)[^>]*>;\s*rel="last"/)
match[1].to_i
else
# If no "last" link, there's only one page of commits
1
end
else
# No Link header means there's only one page
1
end
when 404
Rails.logger.warn "[#{self.class.name}] Repository #{owner}/#{repo} not found for commit count"
0
else
Rails.logger.warn "[#{self.class.name}] Failed to fetch commit count for #{owner}/#{repo}: #{response.status}"
0
end
rescue => e
Rails.logger.error "[#{self.class.name}] Error fetching commit count for #{owner}/#{repo}: #{e.message}"
0
end
end
end

View File

@@ -0,0 +1,23 @@
module RepoHost
class ServiceFactory
def self.for_url(user, repo_url)
case repo_url
when %r{github\.com}
GithubService.new(user, repo_url)
else
raise ArgumentError, "Unsupported repository host: #{repo_url}. Currently only GitHub is supported."
end
end
def self.supported_hosts
%w[github.com]
end
def self.host_for_url(repo_url)
uri = URI.parse(repo_url)
uri.host
rescue URI::InvalidURIError
nil
end
end
end

View File

@@ -6,17 +6,41 @@
<% project_durations.each do |project| %>
<div class="project-duration-card">
<div class="project-header">
<strong title="<%= project[:project] %>">
<%= (project[:project].presence || "Unknown").truncate(12) %>
</strong>
<% if project[:repo_url].present? %>
<%= link_to "🔗", project[:repo_url], target: "_blank" %>
<% end %>
<% if current_user.github_uid.present? && project[:project].present? %>
<%= link_to "✏️", edit_my_project_repo_mapping_path(project_name: project[:project]), class: "edit-repo-link", data: { turbo_frame: '_top'} %>
<% end %>
<div class="project-name-section">
<strong title="<%= project[:project] %>">
<%= (project[:project].presence || "Unknown").truncate(15) %>
</strong>
<% if project[:repository]&.stars.present? %>
<span class="project-stars">⭐ <%= project[:repository].stars %></span>
<% end %>
</div>
<div class="project-actions">
<% if project[:repository]&.homepage.present? %>
<%= link_to "🌐", project[:repository].homepage, target: "_blank", title: "View project website" %>
<% end %>
<% if project[:repo_url].present? %>
<%= link_to "🔗", project[:repo_url], target: "_blank", title: "View repository" %>
<% end %>
<% if current_user.github_uid.present? && project[:project].present? %>
<%= link_to "✏️", edit_my_project_repo_mapping_path(project_name: project[:project]), class: "edit-repo-link", data: { turbo_frame: '_top'}, title: "Edit mapping" %>
<% end %>
</div>
<span class="project-time"><%= short_time_detailed project[:duration] %></span>
</div>
<% if project[:repository]&.description.present? %>
<div class="project-description">
<%= project[:repository].description.truncate(80) %>
</div>
<% end %>
<% if project[:repository]&.formatted_languages.present? %>
<div class="project-languages">
<span class="languages-label">Languages:</span>
<%= project[:repository].formatted_languages %>
</div>
<% end %>
<div class="project-progress-bar">
<div
class="progress"
@@ -24,6 +48,12 @@
background-color: var(--primary-color);"
></div>
</div>
<% if project[:repository]&.last_commit_at.present? %>
<div class="project-meta">
Last commit: <%= time_ago_in_words(project[:repository].last_commit_at) %> ago
</div>
<% end %>
</div>
<% end %>
</div>

View File

@@ -141,6 +141,11 @@ Rails.application.configure do
cleanup_successful_jobs: {
cron: "0 0 * * *",
class: "CleanupSuccessfulJobsJob"
},
sync_stale_repo_metadata: {
cron: "0 4 * * *", # Daily at 4 AM
class: "SyncStaleRepoMetadataJob",
description: "Refreshes repository metadata (stars, commit counts, etc.) for repositories with stale data."
}
}
end

View File

@@ -0,0 +1,20 @@
class CreateRepositories < ActiveRecord::Migration[8.0]
def change
create_table :repositories do |t|
t.string :url
t.string :host
t.string :owner
t.string :name
t.integer :stars
t.text :description
t.string :language
t.text :languages
t.integer :commit_count
t.datetime :last_commit_at
t.datetime :last_synced_at
t.timestamps
end
add_index :repositories, :url, unique: true
end
end

View File

@@ -0,0 +1,6 @@
class UpdateProjectRepoMappingsToUseRepository < ActiveRecord::Migration[8.0]
def change
# Add repository reference
add_reference :project_repo_mappings, :repository, null: true, foreign_key: true
end
end

View File

@@ -0,0 +1,5 @@
class AddRepositoryToCommits < ActiveRecord::Migration[8.0]
def change
add_reference :commits, :repository, null: true, foreign_key: true
end
end

View File

@@ -0,0 +1,5 @@
class AddHomepageToRepositories < ActiveRecord::Migration[8.0]
def change
add_column :repositories, :homepage, :string
end
end

29
db/schema.rb generated
View File

@@ -10,9 +10,12 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_05_27_052632) do
ActiveRecord::Schema[8.0].define(version: 2025_05_30_135145) do
create_schema "pganalyze"
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
enable_extension "pg_stat_statements"
create_table "ahoy_events", force: :cascade do |t|
t.bigint "visit_id"
@@ -74,6 +77,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_27_052632) do
t.jsonb "github_raw"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "repository_id"
t.index ["repository_id"], name: "index_commits_on_repository_id"
t.index ["user_id"], name: "index_commits_on_user_id"
end
@@ -310,7 +315,9 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_27_052632) do
t.string "repo_url", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "repository_id"
t.index ["project_name"], name: "index_project_repo_mappings_on_project_name"
t.index ["repository_id"], name: "index_project_repo_mappings_on_repository_id"
t.index ["user_id", "project_name"], name: "index_project_repo_mappings_on_user_id_and_project_name", unique: true
t.index ["user_id"], name: "index_project_repo_mappings_on_user_id"
end
@@ -333,6 +340,24 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_27_052632) do
t.index ["user_id"], name: "index_repo_host_events_on_user_id"
end
create_table "repositories", force: :cascade do |t|
t.string "url"
t.string "host"
t.string "owner"
t.string "name"
t.integer "stars"
t.text "description"
t.string "language"
t.text "languages"
t.integer "commit_count"
t.datetime "last_commit_at"
t.datetime "last_synced_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "homepage"
t.index ["url"], name: "index_repositories_on_url", unique: true
end
create_table "sailors_log_leaderboards", force: :cascade do |t|
t.string "slack_channel_id"
t.string "slack_uid"
@@ -428,6 +453,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_27_052632) do
end
add_foreign_key "api_keys", "users"
add_foreign_key "commits", "repositories"
add_foreign_key "commits", "users"
add_foreign_key "email_addresses", "users"
add_foreign_key "email_verification_requests", "users"
@@ -437,6 +463,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_27_052632) do
add_foreign_key "leaderboard_entries", "users"
add_foreign_key "mailing_addresses", "users"
add_foreign_key "physical_mails", "users"
add_foreign_key "project_repo_mappings", "repositories"
add_foreign_key "project_repo_mappings", "users"
add_foreign_key "repo_host_events", "users"
add_foreign_key "sign_in_tokens", "users"

27
test/fixtures/repositories.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
url: MyString
host: MyString
owner: MyString
name: MyString
stars: 1
description: MyText
language: MyString
languages: MyText
commit_count: 1
last_commit_at: 2025-05-30 01:50:16
last_synced_at: 2025-05-30 01:50:16
two:
url: MyString
host: MyString
owner: MyString
name: MyString
stars: 1
description: MyText
language: MyString
languages: MyText
commit_count: 1
last_commit_at: 2025-05-30 01:50:16
last_synced_at: 2025-05-30 01:50:16

View File

@@ -0,0 +1,7 @@
require "test_helper"
class RepositoryTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end