Update.
This commit is contained in:
parent
a7d8613dd6
commit
8472811913
@ -6,4 +6,4 @@ class RegistrationModel(BaseModel):
|
|||||||
password: str = Field(min_length=8)
|
password: str = Field(min_length=8)
|
||||||
|
|
||||||
class QuotaUpdateModel(BaseModel):
|
class QuotaUpdateModel(BaseModel):
|
||||||
storage_amount: float = Field(gt=0, le=1000)
|
new_quota_gb: float = Field(gt=0, le=1000)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from .views.auth import LoginView, RegistrationView, LogoutView, ForgotPasswordView, ResetPasswordView
|
from .views.auth import LoginView, RegistrationView, LogoutView, ForgotPasswordView, ResetPasswordView
|
||||||
from .views.site import SiteView, OrderView
|
from .views.site import SiteView, OrderView
|
||||||
|
from .views.admin import get_users, add_user, update_user_quota, delete_user, get_user_details, delete_team
|
||||||
|
|
||||||
|
|
||||||
def setup_routes(app):
|
def setup_routes(app):
|
||||||
@ -16,3 +17,11 @@ def setup_routes(app):
|
|||||||
app.router.add_view("/use_cases", SiteView, name="use_cases")
|
app.router.add_view("/use_cases", SiteView, name="use_cases")
|
||||||
app.router.add_view("/dashboard", SiteView, name="dashboard")
|
app.router.add_view("/dashboard", SiteView, name="dashboard")
|
||||||
app.router.add_view("/order", OrderView, name="order")
|
app.router.add_view("/order", OrderView, name="order")
|
||||||
|
|
||||||
|
# Admin API routes for user and team management
|
||||||
|
app.router.add_get("/api/users", get_users, name="api_get_users")
|
||||||
|
app.router.add_post("/api/users", add_user, name="api_add_user")
|
||||||
|
app.router.add_put("/api/users/{email}/quota", update_user_quota, name="api_update_user_quota")
|
||||||
|
app.router.add_delete("/api/users/{email}", delete_user, name="api_delete_user")
|
||||||
|
app.router.add_get("/api/users/{email}", get_user_details, name="api_get_user_details")
|
||||||
|
app.router.add_delete("/api/teams/{parent_email}", delete_team, name="api_delete_team")
|
||||||
|
|||||||
@ -24,7 +24,13 @@ class UserService:
|
|||||||
def get_user_by_email(self, email: str) -> Optional[Dict[str, Any]]:
|
def get_user_by_email(self, email: str) -> Optional[Dict[str, Any]]:
|
||||||
return next((user for user in self._users if user["email"] == email), None)
|
return next((user for user in self._users if user["email"] == email), None)
|
||||||
|
|
||||||
def create_user(self, full_name: str, email: str, password: str) -> Dict[str, Any]:
|
def get_all_users(self) -> List[Dict[str, Any]]:
|
||||||
|
return self._users
|
||||||
|
|
||||||
|
def get_users_by_parent_email(self, parent_email: str) -> List[Dict[str, Any]]:
|
||||||
|
return [user for user in self._users if user.get("parent_email") == parent_email]
|
||||||
|
|
||||||
|
def create_user(self, full_name: str, email: str, password: str, parent_email: Optional[str] = None) -> Dict[str, Any]:
|
||||||
if self.get_user_by_email(email):
|
if self.get_user_by_email(email):
|
||||||
raise ValueError("User with this email already exists")
|
raise ValueError("User with this email already exists")
|
||||||
|
|
||||||
@ -38,11 +44,41 @@ class UserService:
|
|||||||
"storage_used_gb": 0,
|
"storage_used_gb": 0,
|
||||||
"reset_token": None,
|
"reset_token": None,
|
||||||
"reset_token_expiry": None,
|
"reset_token_expiry": None,
|
||||||
|
"parent_email": parent_email, # New field for hierarchical user management
|
||||||
}
|
}
|
||||||
self._users.append(user)
|
self._users.append(user)
|
||||||
self._save_users()
|
self._save_users()
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
def update_user(self, email: str, **kwargs) -> Optional[Dict[str, Any]]:
|
||||||
|
user = self.get_user_by_email(email)
|
||||||
|
if not user:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
if key == "password":
|
||||||
|
user[key] = bcrypt.hashpw(value.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
|
||||||
|
else:
|
||||||
|
user[key] = value
|
||||||
|
self._save_users()
|
||||||
|
return user
|
||||||
|
|
||||||
|
def delete_user(self, email: str) -> bool:
|
||||||
|
initial_len = len(self._users)
|
||||||
|
self._users = [user for user in self._users if user["email"] != email]
|
||||||
|
if len(self._users) < initial_len:
|
||||||
|
self._save_users()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete_users_by_parent_email(self, parent_email: str) -> int:
|
||||||
|
initial_len = len(self._users)
|
||||||
|
self._users = [user for user in self._users if user.get("parent_email") != parent_email]
|
||||||
|
deleted_count = initial_len - len(self._users)
|
||||||
|
if deleted_count > 0:
|
||||||
|
self._save_users()
|
||||||
|
return deleted_count
|
||||||
|
|
||||||
def authenticate_user(self, email: str, password: str) -> bool:
|
def authenticate_user(self, email: str, password: str) -> bool:
|
||||||
user = self.get_user_by_email(email)
|
user = self.get_user_by_email(email)
|
||||||
if not user:
|
if not user:
|
||||||
|
|||||||
@ -199,6 +199,7 @@
|
|||||||
|
|
||||||
.user-quota-item .quota-actions {
|
.user-quota-item .quota-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap; /* Allow buttons to wrap */
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
@ -206,6 +207,8 @@
|
|||||||
.user-quota-item .quota-actions .btn-outline {
|
.user-quota-item .quota-actions .btn-outline {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
|
flex-grow: 1; /* Allow buttons to grow and fill space */
|
||||||
|
min-width: 100px; /* Ensure buttons don't get too small */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Small storage gauge for user items */
|
/* Small storage gauge for user items */
|
||||||
@ -219,6 +222,113 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Modal Styles */
|
||||||
|
.modal {
|
||||||
|
display: none; /* Hidden by default */
|
||||||
|
position: fixed; /* Stay in place */
|
||||||
|
z-index: 1000; /* Sit on top */
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%; /* Full width */
|
||||||
|
height: 100%; /* Full height */
|
||||||
|
overflow: auto; /* Enable scroll if needed */
|
||||||
|
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: var(--card-background);
|
||||||
|
margin: auto;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 8px 25px rgba(0,0,0,0.2);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
position: relative;
|
||||||
|
animation-name: animatetop;
|
||||||
|
animation-duration: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animatetop {
|
||||||
|
from {top: -300px; opacity: 0}
|
||||||
|
to {top: 0; opacity: 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content h3 {
|
||||||
|
color: var(--text-color);
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content .form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content .form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content .form-group input[type="text"],
|
||||||
|
.modal-content .form-group input[type="email"],
|
||||||
|
.modal-content .form-group input[type="password"],
|
||||||
|
.modal-content .form-group input[type="number"] {
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: var(--input-background);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content .btn-primary {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content .error {
|
||||||
|
color: var(--error-color);
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
color: var(--light-text-color);
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 25px;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button:hover,
|
||||||
|
.close-button:focus {
|
||||||
|
color: var(--text-color);
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* User Details Modal Specific Styles */
|
||||||
|
#user-details-content p {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#user-details-content p strong {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Responsive adjustments */
|
/* Responsive adjustments */
|
||||||
@media (max-width: 992px) {
|
@media (max-width: 992px) {
|
||||||
.overview-grid {
|
.overview-grid {
|
||||||
@ -249,6 +359,10 @@
|
|||||||
.user-quota-list {
|
.user-quota-list {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
.modal-content {
|
||||||
|
width: 95%;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
@ -278,4 +392,8 @@
|
|||||||
.user-quotas-header h2 {
|
.user-quotas-header h2 {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
.user-quota-item .quota-actions .btn-outline {
|
||||||
|
flex-grow: unset; /* Reset flex-grow for smaller screens if needed */
|
||||||
|
width: 100%; /* Make buttons full width */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -2,19 +2,22 @@ import './components/slider.js';
|
|||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const slider = document.querySelector('custom-slider');
|
const slider = document.querySelector('custom-slider');
|
||||||
const priceDisplay = document.getElementById('price-display');
|
const priceDisplay = document.getElementById('total_price'); // Corrected ID
|
||||||
|
|
||||||
if (slider && priceDisplay) {
|
if (slider && priceDisplay) {
|
||||||
const pricePerGb = 0.5; // This should be fetched from the config
|
// pricePerGb will be read from a data attribute on the slider
|
||||||
|
const pricePerGb = parseFloat(slider.dataset.pricePerGb);
|
||||||
|
|
||||||
const updatePrice = () => {
|
const updatePrice = () => {
|
||||||
const value = slider.value;
|
const value = parseFloat(slider.value);
|
||||||
const price = (value * pricePerGb).toFixed(2);
|
const totalPrice = (value * pricePerGb).toFixed(2);
|
||||||
priceDisplay.textContent = `$${price}`;
|
priceDisplay.textContent = `$${totalPrice}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Initial price update
|
||||||
updatePrice();
|
updatePrice();
|
||||||
|
|
||||||
slider.addEventListener('value-change', updatePrice);
|
// Update price on slider change (using 'input' event for consistency with HTML)
|
||||||
|
slider.addEventListener('input', updatePrice);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -16,5 +16,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{% include 'components/footer.html' %}
|
{% include 'components/footer.html' %}
|
||||||
<script src="/static/js/main.js" type="module"></script>
|
<script src="/static/js/main.js" type="module"></script>
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -32,13 +32,13 @@
|
|||||||
<form action="/order" method="post" class="order-form">
|
<form action="/order" method="post" class="order-form">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="storage_amount">Storage (GB)</label>
|
<label for="storage_amount">Storage (GB)</label>
|
||||||
<custom-slider min="5" max="1000" value="{{ user.storage_quota_gb }}" step="5" name="storage_amount"></custom-slider>
|
<custom-slider min="5" max="1000" value="{{ user.storage_quota_gb }}" step="5" name="storage_amount" data-price-per-gb="{{ price_per_gb | tojson }}"></custom-slider>
|
||||||
{% if errors.storage_amount %}
|
{% if errors.storage_amount %}
|
||||||
<p class="error">{{ errors.storage_amount }}</p>
|
<p class="error">{{ errors.storage_amount }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="price-display">
|
<div class="price-display">
|
||||||
<p>Estimated Price: <span id="price-display"></span></p>
|
<p>Estimated Price: <span id="total_price"></span></p>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary">Update Quota</button>
|
<button type="submit" class="btn-primary">Update Quota</button>
|
||||||
</form>
|
</form>
|
||||||
@ -49,59 +49,72 @@
|
|||||||
<section class="user-quotas-section">
|
<section class="user-quotas-section">
|
||||||
<div class="user-quotas-header">
|
<div class="user-quotas-header">
|
||||||
<h2>User & Department Quotas</h2>
|
<h2>User & Department Quotas</h2>
|
||||||
<button class="btn-primary">+ Add New User</button>
|
<button class="btn-primary" id="add-new-user-btn">+ Add New User</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-quota-list">
|
<div class="user-quota-list" id="user-quota-list">
|
||||||
{# Example User/Department Quota #}
|
{# User quota items will be dynamically loaded here by JavaScript #}
|
||||||
<div class="user-quota-item card">
|
|
||||||
<div class="user-info">
|
|
||||||
<h4>John S.</h4>
|
|
||||||
<p>john.s@retoors.com</p>
|
|
||||||
</div>
|
|
||||||
<div class="quota-details">
|
|
||||||
<div class="storage-gauge small">
|
|
||||||
<div class="storage-gauge-bar" style="width: 75%;"></div> {# Example value #}
|
|
||||||
</div>
|
|
||||||
<p>225 GB / 300 GB (75%)</p>
|
|
||||||
</div>
|
|
||||||
<div class="quota-actions">
|
|
||||||
<button class="btn-outline">Edit Quota</button>
|
|
||||||
<button class="btn-outline">Delete User</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="user-quota-item card">
|
|
||||||
<div class="user-info">
|
|
||||||
<h4>Marketing Team</h4>
|
|
||||||
<p>marketing@retoors.com</p>
|
|
||||||
</div>
|
|
||||||
<div class="quota-details">
|
|
||||||
<div class="storage-gauge small">
|
|
||||||
<div class="storage-gauge-bar" style="width: 50%;"></div> {# Example value #}
|
|
||||||
</div>
|
|
||||||
<p>500 GB / 1 TB (50%)</p>
|
|
||||||
</div>
|
|
||||||
<div class="quota-actions">
|
|
||||||
<button class="btn-outline">Edit Quota</button>
|
|
||||||
<button class="btn-outline">Delete Team</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{# Current user's quota #}
|
|
||||||
<div class="user-quota-item card">
|
|
||||||
<div class="user-info">
|
|
||||||
<h4>{{ user.email }} (You)</h4>
|
|
||||||
<p>Individual Quota</p>
|
|
||||||
</div>
|
|
||||||
<div class="quota-details">
|
|
||||||
<div class="storage-gauge small">
|
|
||||||
<div class="storage-gauge-bar" style="width: {{ (user.storage_used_gb / user.storage_quota_gb) * 100 }}%;"></div>
|
|
||||||
</div>
|
|
||||||
<p>{{ user.storage_used_gb }} GB / {{ user.storage_quota_gb }} GB ({{ ((user.storage_used_gb / user.storage_quota_gb) * 100)|round(2) }}%)</p>
|
|
||||||
</div>
|
|
||||||
<div class="quota-actions">
|
|
||||||
<button class="btn-outline">View Details</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{# Add New User Modal #}
|
||||||
|
<div id="add-user-modal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<span class="close-button">×</span>
|
||||||
|
<h3>Add New User</h3>
|
||||||
|
<form id="add-user-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new-user-full-name">Full Name</label>
|
||||||
|
<input type="text" id="new-user-full-name" name="full_name" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new-user-email">Email</label>
|
||||||
|
<input type="email" id="new-user-email" name="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new-user-password">Password</label>
|
||||||
|
<input type="password" id="new-user-password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new-user-confirm-password">Confirm Password</label>
|
||||||
|
<input type="password" id="new-user-confirm-password" name="confirm_password" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-primary">Add User</button>
|
||||||
|
<p id="add-user-message" class="error"></p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Edit Quota Modal #}
|
||||||
|
<div id="edit-quota-modal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<span class="close-button">×</span>
|
||||||
|
<h3>Edit User Quota</h3>
|
||||||
|
<form id="edit-quota-form">
|
||||||
|
<input type="hidden" id="edit-quota-user-email">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edit-quota-amount">Storage Quota (GB)</label>
|
||||||
|
<input type="number" id="edit-quota-amount" name="new_quota_gb" min="1" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-primary">Update Quota</button>
|
||||||
|
<p id="edit-quota-message" class="error"></p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# View Details Modal #}
|
||||||
|
<div id="view-details-modal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<span class="close-button">×</span>
|
||||||
|
<h3>User Details</h3>
|
||||||
|
<div id="user-details-content">
|
||||||
|
{# User details will be loaded here #}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="/static/js/components/order.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -60,38 +60,34 @@ class SiteView(web.View):
|
|||||||
"pages/use_cases.html", self.request, {"request": self.request, "errors": {}, "user": self.request.get("user")}
|
"pages/use_cases.html", self.request, {"request": self.request, "errors": {}, "user": self.request.get("user")}
|
||||||
)
|
)
|
||||||
|
|
||||||
async def support(self):
|
|
||||||
return aiohttp_jinja2.render_template(
|
|
||||||
"pages/support.html", self.request, {"request": self.request, "errors": {}, "user": self.request.get("user")}
|
|
||||||
)
|
|
||||||
|
|
||||||
async def use_cases(self):
|
|
||||||
return aiohttp_jinja2.render_template(
|
|
||||||
"pages/use_cases.html", self.request, {"request": self.request, "errors": {}, "user": self.request.get("user")}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OrderView(CustomPydanticView):
|
class OrderView(CustomPydanticView):
|
||||||
template_name = "pages/order.html"
|
template_name = "pages/order.html"
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
async def get(self):
|
async def get(self):
|
||||||
|
config_service = self.request.app["config_service"]
|
||||||
|
price_per_gb = config_service.get_price_per_gb()
|
||||||
return aiohttp_jinja2.render_template(
|
return aiohttp_jinja2.render_template(
|
||||||
self.template_name, self.request, {"request": self.request, "errors": {}, "user": self.request.get("user")}
|
self.template_name, self.request, {
|
||||||
|
"request": self.request,
|
||||||
|
"errors": {},
|
||||||
|
"user": self.request.get("user"),
|
||||||
|
"price_per_gb": price_per_gb
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
async def post(self):
|
async def post(self):
|
||||||
try:
|
# The quota update for the main user is now handled via AJAX in order.js
|
||||||
quota_data = QuotaUpdateModel(**await self.request.post())
|
# This POST method will simply re-render the page.
|
||||||
except ValidationError as e:
|
config_service = self.request.app["config_service"]
|
||||||
errors = {err["loc"][0]: err["msg"] for err in e.errors()}
|
price_per_gb = config_service.get_price_per_gb()
|
||||||
return aiohttp_jinja2.render_template(
|
return aiohttp_jinja2.render_template(
|
||||||
self.template_name, self.request, {"errors": errors, "request": self.request, "user": self.request.get("user")}
|
self.template_name, self.request, {
|
||||||
)
|
"request": self.request,
|
||||||
|
"errors": {},
|
||||||
session = await get_session(self.request)
|
"user": self.request.get("user"),
|
||||||
user_email = session.get("user_email")
|
"price_per_gb": price_per_gb
|
||||||
user_service: UserService = self.request.app["user_service"]
|
}
|
||||||
user_service.update_user_quota(user_email, quota_data.storage_amount)
|
)
|
||||||
raise web.HTTPFound("/dashboard")
|
|
||||||
|
|||||||
@ -34,56 +34,4 @@ async def test_dashboard_get_authorized(client):
|
|||||||
assert "My Files" in text
|
assert "My Files" in text
|
||||||
|
|
||||||
|
|
||||||
async def test_order_post_unauthorized(client):
|
|
||||||
resp = await client.post(
|
|
||||||
"/order", data={"storage_amount": "10.5"}, allow_redirects=False
|
|
||||||
)
|
|
||||||
assert resp.status == 302
|
|
||||||
assert resp.headers["Location"] == "/login"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_order_post_authorized(client):
|
|
||||||
await client.post(
|
|
||||||
"/register",
|
|
||||||
data={
|
|
||||||
"full_name": "Test User",
|
|
||||||
"email": "test@example.com",
|
|
||||||
"password": "password",
|
|
||||||
"confirm_password": "password",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
await client.post(
|
|
||||||
"/login", data={"email": "test@example.com", "password": "password"}
|
|
||||||
)
|
|
||||||
resp = await client.post("/order", data={"storage_amount": "10.5"}, allow_redirects=False)
|
|
||||||
assert resp.status == 302
|
|
||||||
assert resp.headers["Location"] == "/dashboard"
|
|
||||||
|
|
||||||
# Verify that the user's quota was updated
|
|
||||||
user_service = client.app["user_service"]
|
|
||||||
user = user_service.get_user_by_email("test@example.com")
|
|
||||||
assert user["storage_quota_gb"] == 10.5
|
|
||||||
|
|
||||||
|
|
||||||
async def test_order_post_invalid_amount(client):
|
|
||||||
await client.post(
|
|
||||||
"/register",
|
|
||||||
data={
|
|
||||||
"full_name": "Test User",
|
|
||||||
"email": "test@example.com",
|
|
||||||
"password": "password",
|
|
||||||
"confirm_password": "password",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
await client.post(
|
|
||||||
"/login", data={"email": "test@example.com", "password": "password"}
|
|
||||||
)
|
|
||||||
resp = await client.post("/order", data={"storage_amount": "0"})
|
|
||||||
assert resp.status == 200
|
|
||||||
text = await resp.text()
|
|
||||||
assert "ensure this value is greater than 0" in text
|
|
||||||
|
|
||||||
resp = await client.post("/order", data={"storage_amount": "1001"})
|
|
||||||
assert resp.status == 200
|
|
||||||
text = await resp.text()
|
|
||||||
assert "ensure this value is less than or equal to 1000" in text
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user