Skip to content

Merge feature/inshorturl-fullstack-implementation-flz into main#7

Open
Aaru2006-ai wants to merge 3 commits intoaryazakaria01:mainfrom
Aaru2006-ai:feature/inshorturl-fullstack-implementation-flz
Open

Merge feature/inshorturl-fullstack-implementation-flz into main#7
Aaru2006-ai wants to merge 3 commits intoaryazakaria01:mainfrom
Aaru2006-ai:feature/inshorturl-fullstack-implementation-flz

Conversation

@Aaru2006-ai
Copy link

@Aaru2006-ai Aaru2006-ai commented Nov 13, 2025

Automated PR created from task completion


This PR was created from task: https://cloud.blackbox.ai/tasks/VOFCUkun8E1riQnxKI4p9

Summary by Sourcery

Merge full-stack URL shortener feature into main, introducing a Flask backend with SQLAlchemy, RESTful endpoints, utility modules, static frontend assets, and project documentation.

New Features:

  • Add full-stack Flask-based URL shortener 'inShortUrl' service
  • Expose REST API endpoints for creating, redirecting, and retrieving URL stats and recent links
  • Provide a responsive vanilla JS and CSS frontend for shortening URLs and displaying analytics

Enhancements:

  • Correct YAML dependency name in requirements.txt

Documentation:

  • Add comprehensive documentation for the URL shortener in url_shortener/README.md

@sourcery-ai
Copy link

sourcery-ai bot commented Nov 13, 2025

Reviewer's Guide

This PR introduces a full-stack URL shortener feature (inShortUrl) by adding a Flask backend with SQLAlchemy models and utilities, accompanied by a vanilla JavaScript/CSS/HTML frontend under a new url_shortener directory; it also includes a minor dependency correction in the root requirements.

Class diagram for the new URL model in inShortUrl

classDiagram
    class URL {
        +id: Integer
        +original_url: String
        +short_code: String
        +created_at: DateTime
        +clicks: Integer
        +to_dict()
    }
    class Base
    URL --|> Base: inherits
Loading

File-Level Changes

Change Details Files
Full-stack URL shortener module addition
  • Added Flask application with endpoints for shortening, redirect, stats, recent URLs, health checks, and error handlers
  • Created SQLAlchemy URL model and initialization logic
  • Implemented utility functions for code generation and URL validation
  • Built frontend templates (index.html, 404.html) with responsive design
  • Added CSS styling and JavaScript logic for form handling, API integration, and dynamic recent URLs
  • Provided module README and specific requirements for url_shortener
url_shortener/app.py
url_shortener/models.py
url_shortener/utils.py
url_shortener/templates/index.html
url_shortener/templates/404.html
url_shortener/static/style.css
url_shortener/static/script.js
url_shortener/requirements.txt
url_shortener/README.md
Correct root dependency naming
  • Replaced 'pyyaml' with 'pyaml' in the root requirements.txt
requirements.txt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Blocking issues:

  • User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities (link)
  • User controlled data in a recentList.innerHTML is an anti-pattern that can lead to XSS vulnerabilities (link)
Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `url_shortener/app.py:128` </location>
<code_context>
+    return jsonify({'error': 'Internal server error'}), 500
+
+if __name__ == '__main__':
+    app.run(host='0.0.0.0', port=5000, debug=True)
</code_context>

<issue_to_address>
**🚨 issue (security):** Debug mode is enabled in production entrypoint.

Debug mode should be disabled in production to prevent exposing sensitive information. Make debug configurable via environment variable and default to False.
</issue_to_address>

### Comment 2
<location> `url_shortener/models.py:27-31` </location>
<code_context>
+            'clicks': self.clicks
+        }
+
+def init_db(database_url='sqlite:///urls.db'):
+    engine = create_engine(database_url)
+    Base.metadata.create_all(engine)
+    Session = sessionmaker(bind=engine)
+    return Session()
</code_context>

<issue_to_address>
**issue (bug_risk):** init_db returns a session instance, not a session factory.

Reusing a single session across requests can lead to concurrency problems. Refactor init_db to return the sessionmaker instead, and create a new session for each request.
</issue_to_address>

### Comment 3
<location> `url_shortener/static/script.js:87-97` </location>
<code_context>
+}
+
+function copyToClipboard() {
+    shortUrlInput.select();
+    document.execCommand('copy');
+    
+    const originalText = copyBtn.textContent;
</code_context>

<issue_to_address>
**suggestion:** Clipboard copy uses deprecated execCommand API.

Switch to navigator.clipboard.writeText for improved browser support and to avoid deprecated methods.

```suggestion
    navigator.clipboard.writeText(shortUrlInput.value).then(() => {
        const originalText = copyBtn.textContent;
        copyBtn.textContent = 'Copied!';
        copyBtn.classList.add('copied');

        setTimeout(() => {
            copyBtn.textContent = originalText;
            copyBtn.classList.remove('copied');
        }, 2000);
    });
```
</issue_to_address>

### Comment 4
<location> `url_shortener/static/script.js:141-150` </location>
<code_context>
        recentList.innerHTML = urls.map(url => `
            <div class="recent-item">
                <div class="recent-item-header">
                    <span class="recent-item-code">/${url.short_code}</span>
                    <span class="recent-item-clicks">👁️ ${url.clicks} clicks</span>
                </div>
                <div class="recent-item-url">${truncateUrl(url.original_url, 60)}</div>
                <div class="recent-item-date">${formatDate(url.created_at)}</div>
            </div>
        `).join('');
</code_context>

<issue_to_address>
**security (javascript.browser.security.insecure-document-method):** User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

### Comment 5
<location> `url_shortener/static/script.js:141-150` </location>
<code_context>
        recentList.innerHTML = urls.map(url => `
            <div class="recent-item">
                <div class="recent-item-header">
                    <span class="recent-item-code">/${url.short_code}</span>
                    <span class="recent-item-clicks">👁️ ${url.clicks} clicks</span>
                </div>
                <div class="recent-item-url">${truncateUrl(url.original_url, 60)}</div>
                <div class="recent-item-date">${formatDate(url.created_at)}</div>
            </div>
        `).join('');
</code_context>

<issue_to_address>
**security (javascript.browser.security.insecure-innerhtml):** User controlled data in a `recentList.innerHTML` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

### Comment 6
<location> `url_shortener/app.py:45-52` </location>
<code_context>
@app.route('/api/shorten', methods=['POST'])
def shorten_url():
    """Create a shortened URL"""
    data = request.get_json()

    if not data or 'url' not in data:
        return jsonify({'error': 'URL is required'}), 400

    original_url = data['url']
    custom_code = data.get('custom_code', '').strip()

    # Validate URL
    if not is_valid_url(original_url):
        return jsonify({'error': 'Invalid URL format'}), 400

    # Check if custom code is provided
    if custom_code:
        # Validate custom code (alphanumeric only, 3-10 chars)
        if not custom_code.isalnum() or len(custom_code) < 3 or len(custom_code) > 10:
            return jsonify({'error': 'Custom code must be 3-10 alphanumeric characters'}), 400

        # Check if custom code already exists
        existing = db_session.query(URL).filter_by(short_code=custom_code).first()
        if existing:
            return jsonify({'error': 'Custom code already taken'}), 409

        short_code = custom_code
    else:
        # Check if URL already exists
        existing_url = db_session.query(URL).filter_by(original_url=original_url).first()
        if existing_url:
            return jsonify({
                'short_url': f"{BASE_URL}/{existing_url.short_code}",
                'short_code': existing_url.short_code,
                'original_url': existing_url.original_url,
                'created_at': existing_url.created_at.isoformat(),
                'clicks': existing_url.clicks
            }), 200

        # Generate unique short code
        short_code = generate_short_code()
        while db_session.query(URL).filter_by(short_code=short_code).first():
            short_code = generate_short_code()

    # Create new URL entry
    new_url = URL(original_url=original_url, short_code=short_code)
    db_session.add(new_url)
    db_session.commit()

    return jsonify({
        'short_url': f"{BASE_URL}/{short_code}",
        'short_code': short_code,
        'original_url': original_url,
        'created_at': new_url.created_at.isoformat(),
        'clicks': 0
    }), 201

</code_context>

<issue_to_address>
**issue (code-quality):** Use named expression to simplify assignment and conditional [×2] ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))
</issue_to_address>

### Comment 7
<location> `url_shortener/app.py:97-100` </location>
<code_context>
@app.route('/api/stats/<short_code>')
def get_stats(short_code):
    """Get statistics for a shortened URL"""
    url_entry = db_session.query(URL).filter_by(short_code=short_code).first()

    if not url_entry:
        return jsonify({'error': 'Short URL not found'}), 404

    return jsonify(url_entry.to_dict()), 200

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))
- Lift code into else after jump in control flow ([`reintroduce-else`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/reintroduce-else/))
- Swap if/else branches ([`swap-if-else-branches`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/swap-if-else-branches/))
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

return jsonify({'error': 'Internal server error'}), 500

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): Debug mode is enabled in production entrypoint.

Debug mode should be disabled in production to prevent exposing sensitive information. Make debug configurable via environment variable and default to False.

Comment on lines +27 to +31
def init_db(database_url='sqlite:///urls.db'):
engine = create_engine(database_url)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
return Session()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): init_db returns a session instance, not a session factory.

Reusing a single session across requests can lead to concurrency problems. Refactor init_db to return the sessionmaker instead, and create a new session for each request.

Comment on lines +87 to +97
shortUrlInput.select();
document.execCommand('copy');

const originalText = copyBtn.textContent;
copyBtn.textContent = 'Copied!';
copyBtn.classList.add('copied');

setTimeout(() => {
copyBtn.textContent = originalText;
copyBtn.classList.remove('copied');
}, 2000);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Clipboard copy uses deprecated execCommand API.

Switch to navigator.clipboard.writeText for improved browser support and to avoid deprecated methods.

Suggested change
shortUrlInput.select();
document.execCommand('copy');
const originalText = copyBtn.textContent;
copyBtn.textContent = 'Copied!';
copyBtn.classList.add('copied');
setTimeout(() => {
copyBtn.textContent = originalText;
copyBtn.classList.remove('copied');
}, 2000);
navigator.clipboard.writeText(shortUrlInput.value).then(() => {
const originalText = copyBtn.textContent;
copyBtn.textContent = 'Copied!';
copyBtn.classList.add('copied');
setTimeout(() => {
copyBtn.textContent = originalText;
copyBtn.classList.remove('copied');
}, 2000);
});

Comment on lines +141 to +150
recentList.innerHTML = urls.map(url => `
<div class="recent-item">
<div class="recent-item-header">
<span class="recent-item-code">/${url.short_code}</span>
<span class="recent-item-clicks">👁️ ${url.clicks} clicks</span>
</div>
<div class="recent-item-url">${truncateUrl(url.original_url, 60)}</div>
<div class="recent-item-date">${formatDate(url.created_at)}</div>
</div>
`).join('');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (javascript.browser.security.insecure-document-method): User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

Comment on lines +141 to +150
recentList.innerHTML = urls.map(url => `
<div class="recent-item">
<div class="recent-item-header">
<span class="recent-item-code">/${url.short_code}</span>
<span class="recent-item-clicks">👁️ ${url.clicks} clicks</span>
</div>
<div class="recent-item-url">${truncateUrl(url.original_url, 60)}</div>
<div class="recent-item-date">${formatDate(url.created_at)}</div>
</div>
`).join('');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (javascript.browser.security.insecure-innerhtml): User controlled data in a recentList.innerHTML is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

Comment on lines +45 to +52
existing = db_session.query(URL).filter_by(short_code=custom_code).first()
if existing:
return jsonify({'error': 'Custom code already taken'}), 409

short_code = custom_code
else:
# Check if URL already exists
existing_url = db_session.query(URL).filter_by(original_url=original_url).first()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Use named expression to simplify assignment and conditional [×2] (use-named-expression)

Comment on lines +97 to +100
url_entry = db_session.query(URL).filter_by(short_code=short_code).first()

if not url_entry:
return jsonify({'error': 'Short URL not found'}), 404
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): We've found these issues:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants