feat: add Jinja2 server-side rendering with template components and locust load testing

Introduce a full server-side rendering pipeline using Jinja2Templates, including base layout, navigation, modals, and rant card components. Add a classic.html static fallback page and a locustfile.py for load testing the new endpoints. Update main.py to mount static files, create template directories on startup, and add a format_time helper for relative timestamps.
This commit is contained in:
2025-08-05 01:07:46 +00:00
parent 069f7aea57
commit 7ee3cb1699
13 changed files with 3983 additions and 32 deletions
+133
View File
@@ -0,0 +1,133 @@
<div id="modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2 id="modalTitle">Modal Title</h2>
<button class="close-btn" onclick="closeModal()">&times;</button>
</div>
<div id="modalBody">
<!-- Login Modal -->
<div id="loginModal" style="display: none;">
<h2>Login</h2>
<form action="/login" method="POST">
<div class="alert error" id="loginError"></div>
<div class="form-group">
<label>Username or Email</label>
<input type="text" name="username" required>
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="password" required>
</div>
<button type="submit" class="btn" style="width: 100%;">Login</button>
<p style="text-align: center; margin-top: 1rem;">
Don't have an account? <a href="#" onclick="showModal('register'); return false;">Sign up</a>
</p>
</form>
</div>
<!-- Register Modal -->
<div id="registerModal" style="display: none;">
<h2>Sign Up</h2>
<form onsubmit="submitRegister(event); return false;">
<div class="alert error" id="registerError"></div>
<div class="form-group">
<label>Email</label>
<input type="email" name="email" required>
</div>
<div class="form-group">
<label>Username</label>
<input type="text" name="username" required minlength="4" maxlength="15">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="password" required>
</div>
<button type="submit" class="btn" style="width: 100%;">Sign Up</button>
<p style="text-align: center; margin-top: 1rem;">
Already have an account? <a href="#" onclick="showModal('login'); return false;">Login</a>
</p>
</form>
</div>
<!-- Create Rant Modal -->
<div id="createRantModal" style="display: none;">
<h2>Create Rant</h2>
<form onsubmit="submitCreateRant(event); return false;">
<div class="alert error" id="rantError"></div>
<div class="form-group">
<label>What's on your mind?</label>
<textarea name="rant" placeholder="Share your thoughts..." required></textarea>
</div>
<div class="form-group">
<label>Tags (comma separated)</label>
<input type="text" name="tags" placeholder="rant, javascript, devops">
</div>
<div class="form-group">
<label>Type</label>
<select name="type">
<option value="1">Rant</option>
<option value="2">Collab</option>
<option value="3">Question</option>
<option value="4">devRant</option>
<option value="5">Random</option>
</select>
</div>
<button type="submit" class="btn" style="width: 100%;">Post Rant</button>
</form>
</div>
<!-- Edit Profile Modal -->
<div id="editProfileModal" style="display: none;">
<h2>Edit Profile</h2>
<form onsubmit="submitEditProfile(event); return false;">
<div class="alert success" id="profileSuccess"></div>
<div class="form-group">
<label>About</label>
<textarea name="about" placeholder="Tell us about yourself..."></textarea>
</div>
<div class="form-group">
<label>Skills</label>
<input type="text" name="skills" placeholder="JavaScript, Python, DevOps">
</div>
<div class="form-group">
<label>Location</label>
<input type="text" name="location" placeholder="San Francisco, CA">
</div>
<div class="form-group">
<label>Website</label>
<input type="url" name="website" placeholder="https://example.com">
</div>
<div class="form-group">
<label>GitHub Username</label>
<input type="text" name="github" placeholder="username">
</div>
<button type="submit" class="btn" style="width: 100%;">Update Profile</button>
</form>
</div>
</div>
</div>
</div>
<script>
async function submitRegister(event) {
event.preventDefault();
const formData = new FormData(event.target);
formData.append('app', 3);
formData.append('type', 1);
const response = await fetch('/api/users', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
closeModal();
showModal('login');
alert('Registration successful! Please login.');
} else {
document.getElementById('registerError').textContent = data.error;
document.getElementById('registerError').classList.add('active');
}
}
</script>
+23
View File
@@ -0,0 +1,23 @@
<nav>
<div class="nav-container">
<a href="/" class="logo">Rant</a>
<div class="nav-links">
<a href="/">Feed</a>
<a href="/search">Search</a>
{% if current_user %}
<span>
<a href="/profile/{{ current_user.id }}">Profile</a>
<a href="/notifications">Notifications {% if notif_count > 0 %}<span style="color: var(--error);">({{ notif_count }})</span>{% endif %}</a>
</span>
<span>
<a href="/logout" class="btn btn-secondary">Logout</a>
</span>
{% else %}
<span>
<button class="btn btn-secondary" onclick="showModal('login')">Login</button>
<button class="btn" onclick="showModal('register')">Sign Up</button>
</span>
{% endif %}
</div>
</div>
</nav>
+35
View File
@@ -0,0 +1,35 @@
{% macro render_rant_card(rant, clickable=True) %}
<div class="rant-card" {% if clickable %}onclick="if (!event.target.closest('button') && !event.target.closest('.username')) { window.location.href='/rant/{{ rant.id }}'; }" style="cursor: pointer;"{% endif %}>
<div class="rant-header">
<div class="avatar" style="background: #{{ rant.user_avatar.b }}">
{{ rant.user_username[0]|upper }}
</div>
<div class="user-info">
<div class="username" onclick="event.stopPropagation(); window.location.href='/profile/{{ rant.user_id }}';" style="cursor: pointer;">{{ rant.user_username }}</div>
<div class="score">{{ rant.user_score }} points</div>
</div>
</div>
<div class="rant-content">{{ escape_html(rant.text) }}</div>
{% if rant.attached_image %}
<img src="{{ rant.attached_image }}" alt="Rant image" class="rant-image">
{% endif %}
{% if rant.tags %}
<div class="tags">
{% for tag in rant.tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>
{% endif %}
<div class="rant-footer">
<div class="rant-actions">
<button class="action-btn {% if rant.vote_state == 1 %}voted{% elif rant.vote_state == -1 %}downvoted{% endif %}" onclick="event.stopPropagation(); voteRant({{ rant.id }}, {{ 0 if rant.vote_state == 1 else 1 }})">
++ {{ rant.score }}
</button>
<button class="action-btn" onclick="event.stopPropagation(); window.location.href='/rant/{{ rant.id }}';">
💬 {{ rant.num_comments }}
</button>
</div>
<div>{{ format_time(rant.created_time) }}</div>
</div>
</div>
{% endmacro %}