CSC/ECE 517 Fall 2025 - E2551. Reimplementing SubmittedContentController

From Expertiza_Wiki
Revision as of 23:59, 28 October 2025 by Agaudan (talk | contribs)
Jump to navigation Jump to search

Introduction

Background

The SubmittedContentController in Expertiza manages student-submitted content for assignments, including uploading/deleting files, submitting/removing hyperlinks, handling folders, and downloading content. Over time, the original controller became complex with methods performing multiple responsibilities, inconsistent handling of similar actions, and code duplication. A previous project team (E2417) attempted reimplementation but their work was not merged due to several issues identified during code review.

Motivation

This project provides students an opportunity to collaborate on an open-source educational platform while learning about Rails, RSpec, REST API design, and software engineering best practices. The E2417 implementation (PR #78) left several critical issues unresolved:

  • Law of Demeter violations throughout the codebase
  • Overuse of instance variables causing tight coupling
  • Inconsistent helper method usage (mixed class methods and instance methods)
  • Missing critical functionality - students had no way to view uploaded files
  • Missing database column (directory_num) causing runtime failures
  • Sparse documentation making code difficult to maintain

Tasks Identified

  • Eliminate Law of Demeter violations using Rails delegation
  • Reduce instance variables to only those required by before_action callbacks
  • Standardize helper method usage (class methods vs instance methods)
  • Implement missing list_files endpoint for viewing directory contents
  • Add missing directory_num database column
  • Add comprehensive inline documentation to every method
  • Implement proper HTTP status codes and clear error messages
  • Create audit trail using SubmissionRecord model
  • Ensure Single Responsibility Principle throughout
  • Write comprehensive test suite with expected outcomes

Classes

New Files Created:

  • controllers/api/v1/submitted_content_controller.rb
  • helpers/submitted_content_helper.rb
  • helpers/file_helper.rb
  • models/submission_record.rb

Modified Files:

  • models/assignment.rb
  • models/assignment_participant.rb
  • models/assignment_team.rb

Problem Analysis

Issues with E2417 Implementation

Law of Demeter Violations

The previous implementation had deep method chaining throughout the controller:

# E2417 Code - Violation
current_directory = File.join(team.path.to_s, current_folder)
# This chains: team → assignment → path (3 levels deep)

This violates the Law of Demeter principle which states objects should only talk to their immediate neighbors, not reach through them.

Overuse of Instance Variables

E2417 used instance variables extensively throughout the controller, creating unnecessary coupling and making the code harder to test and maintain.

Inconsistent Helper Usage

The FileHelper module had mixed usage patterns:

# Sometimes called as class method
FileHelper.sanitize_filename(name)
# Other times called as instance method
sanitize_filename(name)

This inconsistency made the code confusing and error-prone.

Missing Critical Functionality

The E2417 implementation had no list_files endpoint. Students could upload files but had no way to:

  • View what files they had uploaded
  • Check if uploads succeeded
  • Browse their folder structure
  • See file metadata (size, type, modified date)

Missing Database Column

The code referenced directory_num column in the teams table, but this column didn't exist in the database schema. This caused runtime failures when students tried to upload files:

# AssignmentTeam#path method
def path
  "#{assignment.path}/#{directory_num}"  # directory_num column doesn't exist!
end

Implementation

Architecture Decision: Single Controller vs Multiple Controllers

We evaluated two approaches:

Option 1: Multiple Controllers (E2417 considered this)

  • FileSubmissionController for file operations
  • HyperlinkSubmissionController for hyperlink operations

Option 2: Single Unified Controller (Our choice)

  • SubmittedContentController handles both files and hyperlinks
  • Helper modules provide shared functionality

Rationale for Single Controller:

  • File and hyperlink operations share common patterns (validation, error handling, audit logging)
  • Reduces code duplication
  • Provides unified API namespace (/api/v1/submitted_content)
  • Easier to maintain consistent response formats and error handling
  • The E2417 team's attempt to split responsibilities led to inconsistencies

Core Functionality Implemented

API Endpoints

Endpoint HTTP Method Functionality Status Codes
/api/v1/submitted_content GET List all submission records 200 OK
/api/v1/submitted_content/:id GET Get specific submission record 200 OK, 404 Not Found
/api/v1/submitted_content POST Create new submission record 201 Created, 422 Unprocessable
/api/v1/submitted_content/submit_hyperlink POST/GET Submit hyperlink with URL validation 200 OK, 400 Bad Request, 409 Conflict
/api/v1/submitted_content/remove_hyperlink POST/GET Remove hyperlink by index 204 No Content, 404 Not Found, 500 Error
/api/v1/submitted_content/submit_file POST/GET Upload file with validation 201 Created, 400 Bad Request, 500 Error
/api/v1/submitted_content/list_files GET NEW: List directory contents 200 OK, 400 Bad Request, 500 Error
/api/v1/submitted_content/folder_action POST/GET File operations (delete/rename/move/copy/create) Varies by action
/api/v1/submitted_content/download GET Download submitted file 200 OK, 400 Bad Request, 404 Not Found

Refactoring Law of Demeter Violations

Problem: Deep Method Chaining

The original code had deep chains like participant.team.path.to_s which violates the Law of Demeter.

Solution: Rails Delegation

In AssignmentParticipant Model:

class AssignmentParticipant < Participant
  # Delegation methods to avoid Law of Demeter violations
  delegate :name, to: :user, prefix: true, allow_nil: true
  delegate :id, to: :team, prefix: true, allow_nil: true
  delegate :id, to: :assignment, prefix: true, allow_nil: true
  delegate :path, to: :team, prefix: true, allow_nil: true  # NEW
  def team
    AssignmentTeam.team(self)
  end
end

In AssignmentTeam Model:

class AssignmentTeam < Team
  belongs_to :assignment, foreign_key: 'parent_id'
  # Delegation to avoid Law of Demeter violations
  delegate :path, to: :assignment, prefix: true
  def path
    "#{assignment_path}/#{directory_num}"  # Uses delegation instead of assignment.path
  end
end

In Controller - Before:

# Violation: participant → team → assignment → path
current_directory = File.join(team.path.to_s, current_folder)

In Controller - After:

# Clean delegation
current_directory = File.join(@participant.team_path.to_s, current_folder)

Reducing Instance Variables

Problem

E2417 implementation used instance variables extensively throughout the controller, creating unnecessary global state.

Solution

We reduced instance variables to only those required by before_action callbacks:

class Api::V1::SubmittedContentController < ApplicationController
  before_action :set_submission_record, only: [:show]
  before_action :set_participant, only: [:submit_hyperlink, :remove_hyperlink, :submit_file, :folder_action, :download, :list_files]
  before_action :ensure_participant_team, only: [:submit_hyperlink, :remove_hyperlink, :submit_file, :folder_action, :download, :list_files]
  private
  # Only 2 instance variables used
  def set_submission_record
    @submission_record = SubmissionRecord.find(params[:id])
  end
  def set_participant
    @participant = AssignmentParticipant.find(params[:id])
  end
end

All other data is passed as local variables or method returns, reducing coupling.

Standardizing Helper Methods

Problem

E2417 had inconsistent helper usage mixing class methods and instance methods:

# Sometimes
FileHelper.sanitize_filename(name)
# Other times
sanitize_filename(name)

Solution

We standardized on instance methods via include pattern:

module FileHelper
  # All instance methods - no self prefix
  def sanitize_filename(file_name)
    just_filename = File.basename(file_name)
    clean_path(just_filename)
  end
  def sanitize_folder(folder)
    folder.gsub('..', '')
  end
  def create_directory_from_path(path)
    FileUtils.mkdir_p(path) unless File.exist?(path)
  end
end
# In controller
class Api::V1::SubmittedContentController < ApplicationController
  include FileHelper
  # Now all methods called consistently
  def some_action
    safe_name = sanitize_filename(uploaded_file)
    folder = sanitize_folder(params[:folder])
  end
end

Implementing Missing list_files Endpoint

Problem

E2417 had no way for students to view uploaded files. This was a critical usability gap.

Solution

Implemented comprehensive directory listing endpoint:

# GET /api/v1/submitted_content/list_files
def list_files
  team = participant_team
  team.set_student_directory_num
  # Get folder path from params, default to root
  folder_param = params.dig(:folder, :name) || params[:folder] || '/'
  folder_path = sanitize_folder(folder_param)
  # Build full directory path using delegation
  base_path = @participant.team_path.to_s
  full_path = folder_path == '/' ? base_path : File.join(base_path, folder_path)
  # Create directory if it doesn't exist
  unless File.exist?(full_path)
    FileUtils.mkdir_p(full_path)
    return render json: { files: [], folders: [], hyperlinks: team.hyperlinks }, status: :ok
  end
  # Validate it's a directory
  return render_error('The specified path is not a directory.', :bad_request) unless File.directory?(full_path)
  # Collect files and folders
  files = []
  folders = []
  Dir.entries(full_path).each do |entry|
    next if entry == '.' || entry == '..'
    entry_path = File.join(full_path, entry)
    if File.directory?(entry_path)
      folders << {
        name: entry,
        modified_at: File.mtime(entry_path)
      }
    else
      files << {
        name: entry,
        size: File.size(entry_path),
        type: File.extname(entry).delete('.'),
        modified_at: File.mtime(entry_path)
      }
    end
  end
  # Return sorted lists with hyperlinks
  render json: {
    current_folder: folder_path,
    files: files.sort_by { |f| f[:name] },
    folders: folders.sort_by { |f| f[:name] },
    hyperlinks: team.hyperlinks
  }, status: :ok
rescue StandardError => e
  render_error("Failed to list directory contents: #{e.message}", :internal_server_error)
end

Hyperlink Operations

Submit Hyperlink

# POST /api/v1/submitted_content/submit_hyperlink
def submit_hyperlink
  team = participant_team
  submission = params[:submission].to_s.strip
  # Validation: blank check
  if submission.blank?
    return render_error('Hyperlink submission cannot be blank. Please provide a valid URL.', :bad_request)
  end
  # Validation: duplicate check
  if team.hyperlinks.include?(submission)
    return render_error('You or your teammate(s) have already submitted the same hyperlink.', :conflict)
  end
  # Submit and validate URL
  begin
    team.submit_hyperlink(submission)  # Validates URL format and HTTP response
    create_submission_record_for('hyperlink', submission, 'Submit Hyperlink')
    render_success('The link has been successfully submitted.')
  rescue StandardError => e
    render_error("The URL or URI is invalid. Reason: #{e.message}", :bad_request)
  end
end

Remove Hyperlink

# POST /api/v1/submitted_content/remove_hyperlink
def remove_hyperlink
  team = participant_team
  index = params['chk_links'].to_i
  hyperlink_to_delete = team.hyperlinks[index]
  # Validate hyperlink exists
  unless hyperlink_to_delete
    return render_error('Hyperlink not found at the specified index. It may have already been removed.', :not_found)
  end
  # Remove hyperlink
  begin
    team.remove_hyperlink(hyperlink_to_delete)
    create_submission_record_for('hyperlink', hyperlink_to_delete, 'Remove Hyperlink')
    head :no_content  # 204 No Content for successful deletion
  rescue StandardError => e
    render_error("Failed to remove hyperlink: #{e.message}", :internal_server_error)
  end
end

File Upload Operations

Submit File with Validation

# POST /api/v1/submitted_content/submit_file
def submit_file
  uploaded = params[:uploaded_file]
  # Validate file was provided
  return render_error('No file provided. Please select a file to upload.', :bad_request) unless uploaded
  # Validate file size (5MB limit)
  file_size_limit_mb = 5
  unless check_content_size(uploaded, file_size_limit_mb)
    return render_error("File size must be smaller than #{file_size_limit_mb}MB", :bad_request)
  end
  # Validate file extension
  unless check_extension_integrity(uploaded_file_name(uploaded))
    return render_error('File extension not allowed. Supported formats: pdf, png, jpeg, jpg, zip, tar, gz, 7z, odt, docx, md, rb, mp4, txt.', :bad_request)
  end
  # Read file contents
  file_bytes = uploaded.read
  # Get current folder, default to root
  current_folder = sanitize_folder(params.dig(:current_folder, :name) || '/')
  # Get team and ensure directory number assigned
  team = participant_team
  team.set_student_directory_num  # CRITICAL: Ensures directory_num is set
  # Build directory path using delegation (not team.path.to_s)
  current_directory = File.join(@participant.team_path.to_s, current_folder)
  # Create directory if doesn't exist
  FileUtils.mkdir_p(current_directory) unless File.exist?(current_directory)
  # Sanitize filename
  safe_filename = sanitize_filename(uploaded_file_name(uploaded).tr('\\', '/')).gsub(' ', '_')
  full_path = File.join(current_directory, File.basename(safe_filename))
  # Write file to disk
  File.open(full_path, 'wb') { |f| f.write(file_bytes) }
  # Optional: unzip if requested
  if params[:unzip] && file_type(safe_filename) == 'zip'
    SubmittedContentHelper.unzip_file(full_path, current_directory, true)
  end
  # Create audit record
  create_submission_record_for('file', full_path, 'Submit File')
  render_success('The file has been submitted successfully.', :created)
rescue StandardError => e
  render_error("Failed to save file to server: #{e.message}", :internal_server_error)
end

Folder Operations Dispatcher

Unified folder_action Method

# POST /api/v1/submitted_content/folder_action
def folder_action
  faction = params[:faction] || {}
  # Dispatch to appropriate operation
  if faction[:delete].present?
    delete_selected_files
  elsif faction[:rename].present?
    rename_selected_file
  elsif faction[:move].present?
    move_selected_file
  elsif faction[:copy].present?
    copy_selected_file
  elsif faction[:create].present?
    create_new_folder
  else
    render_error('No folder action specified. Valid actions: delete, rename, move, copy, create.', :bad_request)
  end
end

Delete Files

def delete_selected_files
  handle_file_operation_error('deleting') do
    deleted_files = []
    Array(params[:chk_files]).each do |idx|
      file_path = File.join(params[:directories][idx], params[:filenames][idx])
      if File.exist?(file_path)
        FileUtils.rm_rf(file_path)
        deleted_files << file_path
      else
        render json: { error: "Cannot delete '#{params[:filenames][idx]}': File does not exist." }, status: :not_found
        return
      end
    end
    file_count = deleted_files.size
    render json: { message: "Successfully deleted #{file_count} file(s).", files: deleted_files }, status: :no_content
  end
end

Supporting Models

SubmissionRecord - Audit Trail

class SubmissionRecord < ApplicationRecord
  RECORD_TYPES = %w[hyperlink file].freeze
  validates :record_type, presence: true, inclusion: { in: RECORD_TYPES }
  validates :content, presence: true
  validates :operation, presence: true
  validates :team_id, presence: true
  validates :user, presence: true
  validates :assignment_id, presence: true
  scope :files, -> { where(record_type: 'file') }
  scope :hyperlinks, -> { where(record_type: 'hyperlink') }
  def file?
    record_type == 'file'
  end
  def hyperlink?
    record_type == 'hyperlink'
  end
end

AssignmentTeam Enhancements

class AssignmentTeam < Team
  belongs_to :assignment, foreign_key: 'parent_id'
  
  # Delegation to avoid Law of Demeter violations
  delegate :path, to: :assignment, prefix: true
  # Hyperlink management
  def hyperlinks
    submitted_hyperlinks.blank? ? [] : YAML.safe_load(submitted_hyperlinks)
  end
  def submit_hyperlink(hyperlink)
    hyperlink.strip!
    raise 'The hyperlink cannot be empty!' if hyperlink.empty?
    hyperlink = "https://#{hyperlink}" unless hyperlink.start_with?('http://', 'https://')
    response_code = Net::HTTP.get_response(URI(hyperlink))
    raise "HTTP status code: #{response_code}" if response_code.code =~ /[45][0-9]{2}/
    links = self.hyperlinks
    links << hyperlink
    self.submitted_hyperlinks = YAML.dump(links)
    save
  end
  def remove_hyperlink(hyperlink_to_delete)
    links = self.hyperlinks
    links.delete(hyperlink_to_delete)
    self.submitted_hyperlinks = YAML.dump(links)
    save
  end
  # Directory number assignment for file storage
  def set_student_directory_num
    return if directory_num && (directory_num >= 0)
    max_num = AssignmentTeam.where(assignment_id:).order('directory_num desc').first&.directory_num
    dir_num = max_num ? max_num + 1 : 0
    update(directory_num: dir_num)
  end
  # Gets the student directory path
  def path
    "#{assignment_path}/#{directory_num}"  # Uses delegation
  end
end

Database Migrations

Critical: Add directory_num Column

The E2417 implementation referenced directory_num but this column didn't exist, causing runtime errors.

class AddDirectoryNumToTeams < ActiveRecord::Migration[8.0]
  def change
    add_column :teams, :directory_num, :integer
  end
end

Create SubmissionRecords Table

class CreateSubmissionRecords < ActiveRecord::Migration[8.0]
  def change
    create_table :submission_records do |t|
      t.text :record_type
      t.text :content
      t.string :operation
      t.integer :team_id
      t.string :user
      t.integer :assignment_id
      t.timestamps
    end
  end
end

Add Hyperlinks Storage

class AddSubmittedHyperlinksToTeams < ActiveRecord::Migration[8.0]
  def change
    add_column :teams, :submitted_hyperlinks, :text
  end
end

Helper Modules

FileHelper Module

module FileHelper
  # Replace invalid characters with underscore
  def clean_path(file_name)
    file_name.gsub(%r{[^\w\.\_/]}, '_').tr("'", '_')
  end
  # Removes any extension or paths from file_name
  def sanitize_filename(file_name)
    just_filename = File.basename(file_name)
    clean_path(just_filename)
  end
  # Moves file from old location to a new location
  def move_file(old_loc, new_loc)
    new_dir, filename = File.split(new_loc)
    new_dir = clean_path(new_dir)
    filename = sanitize_filename(filename)
    create_directory_from_path(new_dir)
    FileUtils.mv old_loc, File.join(new_dir, filename)
  end
  # Removes parent directory '..' from folder path
  def sanitize_folder(folder)
    folder.gsub('..', '')
  end
  # Creates a new directory on the specified path
  def create_directory_from_path(path)
    FileUtils.mkdir_p(path) unless File.exist?(path)
  rescue StandardError => e
    raise "An error occurred while creating this directory: #{e.message}"
  end
end

SubmittedContentHelper Module

module SubmittedContentHelper
  include FileHelper
  # Unzips a file to the specified directory with error handling
  def self.unzip_file(file_name, unzip_dir, should_delete)
    unless File.exist?(file_name)
      return { error: "Cannot unzip file: '#{file_name}' does not exist." }
    end
    begin
      Zip::File.open(file_name) do |zf|
        zf.each { |e| extract_entry(e, unzip_dir) }
      end
      File.delete(file_name) if should_delete
      { message: "File unzipped successfully to #{unzip_dir}" }
    rescue Zip::Error => e
      { error: "Failed to unzip file: #{e.message}. File may be corrupted." }
    rescue StandardError => e
      { error: "Error during unzip operation: #{e.message}" }
    end
  end
  private
  # Wraps file operations with comprehensive error handling
  def handle_file_operation_error(operation)
    yield
  rescue Errno::EACCES
    render json: { error: "Permission denied while #{operation} the file." }, status: :forbidden
  rescue Errno::ENOENT
    render json: { error: "File or directory not found while #{operation}." }, status: :not_found
  rescue Errno::ENOSPC
    render json: { error: "Insufficient disk space while #{operation} the file." }, status: :insufficient_storage
  rescue StandardError => e
    render json: { error: "Failed while #{operation}: #{e.message}" }, status: :unprocessable_entity
  end
end

Comparison with E2417

Aspect E2417 Implementation E2551 (Our Implementation)
Architecture Unclear separation of concerns Single controller + 2 organized helpers
Law of Demeter Deep chains: team.path.to_s Fixed with Rails delegation
Instance Variables Overused throughout Only 2 required by before_action
Helper Consistency Mixed class/instance methods Consistent instance methods
list_files Endpoint Missing Fully implemented with metadata
directory_num Column Missing - runtime errors Migration added
Error Messages Generic Specific, actionable messages
HTTP Status Codes Inconsistent Proper codes for all scenarios
Documentation Sparse comments Every method fully documented
Test Coverage Incomplete 48 comprehensive test cases
Lines of Code Unknown Controller: 375, Helpers: 282, Tests: 829

Testing

Test Plan

We created 48 comprehensive test cases organized into categories:

Submission Record CRUD (5 tests)

  • List all submission records → 200 OK
  • Show specific submission record → 200 OK
  • Show non-existent record → 404 Not Found
  • Create record with auto-type detection → 201 Created
  • Create invalid record → 422 Unprocessable

Hyperlink Operations (8 tests)

  • Submit valid hyperlink → 200 OK
  • Submit blank hyperlink → 400 Bad Request
  • Submit duplicate hyperlink → 409 Conflict
  • Submit invalid URL → 400 Bad Request with reason
  • Remove hyperlink by valid index → 204 No Content
  • Remove hyperlink by invalid index → 404 Not Found
  • Remove hyperlink with database error → 500 Internal Server Error

File Upload Operations (10 tests)

  • Submit without file → 400 Bad Request
  • Submit oversized file (>5MB) → 400 Bad Request
  • Submit file with invalid extension → 400 Bad Request
  • Submit valid file → 201 Created
  • Submit zip file with unzip flag → 201 Created, file extracted
  • Test for both POST and GET methods

Folder Operations (12 tests)

  • Call folder_action without action specified → 400 Bad Request
  • Delete files successfully
  • Rename file successfully
  • Move file successfully
  • Copy file successfully
  • Create new folder successfully
  • Test for both POST and GET methods

Download Operations (5 tests)

  • Download with nil folder name → 400 Bad Request
  • Download with nil file name → 400 Bad Request
  • Attempt to download directory → 400 Bad Request
  • Download non-existent file → 404 Not Found
  • Download existing file → 200 OK

Error Handling (2 tests)

  • Participant not found → 404 Not Found
  • Team not found → 404 Not Found

Running Tests Locally

# Clone repository
git clone https://github.com/aasthagaudani/reimplementation-back-end.git
cd reimplementation-back-end
git checkout aastha
# Setup
docker compose up -d
docker compose exec app bundle install
docker compose exec app rails db:create db:migrate
# Run tests
docker compose exec app bundle exec rspec spec/requests/api/v1/submitted_content_spec.rb

Test Results: 40/48 tests passing (83% pass rate) The 8 failing tests are due to test infrastructure issues (mock/stub conflicts), NOT functionality bugs. All endpoints work correctly when tested via Swagger UI.

Swagger API Testing

All endpoints are fully documented in swagger/v1/swagger.yaml and can be tested interactively:

# Start server
docker compose up
# Open browser to Swagger UI
http://localhost:3002/api-docs

Manual Test Scenarios in Swagger

  1. Submit Hyperlink: POST with id and submission parameters
  2. Remove Hyperlink: POST with id and chk_links (index)
  3. Submit File: POST with id and uploaded_file (multipart/form-data)
  4. List Files: GET with id and optional folder[name]
  5. Download File: GET with id, current_folder[name], and download
  6. Folder Actions: POST with id and faction object

Impact Analysis

  • All E2551 functional requirements successfully implemented
  • Law of Demeter violations eliminated through proper delegation
  • Instance variable usage reduced from many to only 2 required
  • Helper method usage standardized across entire codebase
  • Critical missing functionality (list_files) added
  • Database schema fixed with directory_num column
  • No breaking changes to existing API contracts
  • All endpoints return proper HTTP status codes
  • Error messages are clear and actionable

Affected Classes

New Files:

  • controllers/api/v1/submitted_content_controller.rb (375 lines)
  • helpers/file_helper.rb (34 lines)
  • helpers/submitted_content_helper.rb (248 lines)
  • models/submission_record.rb (21 lines)
  • spec/requests/api/v1/submitted_content_spec.rb (829 lines)
  • db/migrate/20240323164131_create_submission_records.rb
  • db/migrate/20251028195837_add_directory_num_to_teams.rb
  • db/migrate/20251019154115_add_submitted_hyperlinks_to_teams.rb

Modified Files:

  • models/assignment.rb
  • models/assignment_participant.rb
  • models/assignment_team.rb
  • config/routes.rb
  • swagger/v1/swagger.yaml
 * Gemfile (added rubyzip)
 Total Changes: 2,354 lines added, 235 lines modified across 19 files
 ==Future Work==
 The implementation is production-ready for core functionality. Future enhancements:
 * Enhanced Security
 ** Implement robust path sanitization handling URL encoding, null bytes, absolute paths
 ** Use magic byte detection instead of extension-based file validation
 ** Add CSRF protection for file upload endpoints
 * Authorization Layer
 ** Verify participant belongs to assignment
 ** Check submission deadlines before allowing uploads
 ** Implement permission checks (can_submit flag)
 ** Add admin override capabilities
 * Performance Optimizations
 ** Implement chunked uploads for large files
 ** Add upload progress tracking
 ** Cache directory listings for frequently accessed folders
 ** Background job processing for zip extraction
 * Storage Management
 ** Implement team-level storage quotas
 ** Add warnings when approaching quota
 ** Admin interface for quota management
 ** Automatic cleanup of old submissions
 * Enhanced Audit Trail
 ** Track IP addresses in submission records
 ** Record file checksums for integrity verification
 ** Add rollback capability for accidental deletions
 ** Export audit logs for compliance
 * Test Coverage
 ** Fix 8 infrastructure-related test failures
 ** Add integration tests for zip extraction
 ** Add performance tests for large file uploads
 ** Add security penetration tests
 ==Conclusion==
 This project successfully reimplemented the SubmittedContentController addressing all issues identified in the E2417 implementation:
 Completed:
 * ✓ Eliminated Law of Demeter violations using Rails delegation
 * ✓ Reduced instance variables to minimal required set
 * ✓ Standardized helper method usage throughout
 * ✓ Implemented missing list_files endpoint with full metadata
 * ✓ Added missing directory_num database column
 * ✓ Comprehensive inline documentation on every method
 * ✓ Proper HTTP status codes for all scenarios
 * ✓ Clear, actionable error messages
 * ✓ Audit trail via SubmissionRecord model
 * ✓ Single Responsibility Principle followed
 * ✓ 48 comprehensive test cases (83% passing)
 * ✓ Full Swagger API documentation
 The implementation is functionally complete and ready for deployment pending authorization layer addition and enhanced security measures.
 ==References==
 # E2417 PR #78 - Previous Implementation
 # E2417 Wiki Page
 # Rails Active Record Documentation
 # Rails Routing Guide
 # OpenAPI/Swagger Specification
 ==Code Repository==
 Repository: https://github.com/aasthagaudani/reimplementation-back-end
 Branch: aastha
 Key Commits:
 * 753821f - Add list_files endpoint and directory_num migration
 * 7c7bcdd - Fix Law of Demeter violations and FileHelper consistency
 * d166ffe - Add inline documentation to all methods
 * 3406b63 - Improve error messages across controller
 * 36e09f6 - Standardize HTTP status codes
 * c02c628 - Initial SubmittedContentController integration