feat: csv user import frontend

This commit is contained in:
Timothy J. Baek 2024-05-01 19:02:25 -07:00
parent e5703a588f
commit bd3b5f1edb
4 changed files with 188 additions and 54 deletions

View file

@ -1,12 +1,14 @@
import logging
from fastapi import Request
from fastapi import Request, UploadFile, File
from fastapi import Depends, HTTPException, status
from fastapi import APIRouter
from pydantic import BaseModel
import re
import uuid
import csv
from apps.web.models.auths import (
SigninForm,
@ -212,7 +214,7 @@ async def signup(request: Request, form_data: SignupForm):
@router.post("/add", response_model=SigninResponse)
async def signup(form_data: AddUserForm, user=Depends(get_admin_user)):
async def add_user(form_data: AddUserForm, user=Depends(get_admin_user)):
if not validate_email_format(form_data.email.lower()):
raise HTTPException(
@ -251,6 +253,76 @@ async def signup(form_data: AddUserForm, user=Depends(get_admin_user)):
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
@router.post("/add/import", response_model=SigninResponse)
async def add_user_csv_import(
file: UploadFile = File(...), user=Depends(get_admin_user)
):
try:
# Check if the file is a CSV file
if file.filename.endswith(".csv"):
# Read the contents of the CSV file
contents = await file.read()
# Decode the contents from bytes to string
decoded_content = contents.decode("utf-8")
# Split the CSV content into lines
csv_lines = decoded_content.split("\n")
# Parse CSV
csv_data = []
csv_reader = csv.reader(csv_lines)
for row in csv_reader:
csv_data.append(row)
# Print the CSV data
for row in csv_data:
print(row)
return {"message": "CSV file uploaded successfully."}
else:
raise "File must be a CSV."
except Exception as err:
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
# if not validate_email_format(form_data.email.lower()):
# raise HTTPException(
# status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT
# )
# if Users.get_user_by_email(form_data.email.lower()):
# raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
# try:
# print(form_data)
# hashed = get_password_hash(form_data.password)
# user = Auths.insert_new_auth(
# form_data.email.lower(),
# hashed,
# form_data.name,
# form_data.profile_image_url,
# form_data.role,
# )
# if user:
# token = create_token(data={"id": user.id})
# return {
# "token": token,
# "token_type": "Bearer",
# "id": user.id,
# "email": user.email,
# "name": user.name,
# "role": user.role,
# "profile_image_url": user.profile_image_url,
# }
# else:
# raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR)
# except Exception as err:
# raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
############################
# ToggleSignUp
############################

View file

@ -0,0 +1 @@
Name,Email,Password,Role
1 Name Email Password Role

View file

@ -5,12 +5,16 @@
import { addUser } from '$lib/apis/auths';
import Modal from '../common/Modal.svelte';
import { WEBUI_BASE_URL } from '$lib/constants';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
export let show = false;
let tab = '';
let inputFiles;
let _user = {
name: '',
email: '',
@ -76,69 +80,126 @@
submitHandler();
}}
>
<div class=" ">
<div class="flex flex-col w-full">
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Role')}</div>
<div class="flex text-center text-sm font-medium rounded-xl bg-transparent/10 p-1 mb-2">
<button
class="w-full rounded-lg p-1.5 {tab === '' ? 'bg-gray-50 dark:bg-gray-850' : ''}"
type="button"
on:click={() => {
tab = '';
}}>Form</button
>
<div class="flex-1">
<select
class="w-full capitalize rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
bind:value={_user.role}
placeholder={$i18n.t('Enter Your Role')}
required
>
<option value="pending"> pending </option>
<option value="user"> user </option>
<option value="admin"> admin </option>
</select>
<button
class="w-full rounded-lg p-1 {tab === 'import' ? 'bg-gray-50 dark:bg-gray-850' : ''}"
type="button"
on:click={() => {
tab = 'import';
}}>CSV Import</button
>
</div>
<div class="px-1">
{#if tab === ''}
<div class="flex flex-col w-full">
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Role')}</div>
<div class="flex-1">
<select
class="w-full capitalize rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
bind:value={_user.role}
placeholder={$i18n.t('Enter Your Role')}
required
>
<option value="pending"> pending </option>
<option value="user"> user </option>
<option value="admin"> admin </option>
</select>
</div>
</div>
</div>
<div class="flex flex-col w-full mt-2">
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name')}</div>
<div class="flex flex-col w-full mt-2">
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name')}</div>
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
type="text"
bind:value={_user.name}
placeholder={$i18n.t('Enter Your Full Name')}
autocomplete="off"
required
/>
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
type="text"
bind:value={_user.name}
placeholder={$i18n.t('Enter Your Full Name')}
autocomplete="off"
required
/>
</div>
</div>
</div>
<hr class=" dark:border-gray-800 my-3 w-full" />
<hr class=" dark:border-gray-800 my-3 w-full" />
<div class="flex flex-col w-full">
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Email')}</div>
<div class="flex flex-col w-full">
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Email')}</div>
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
type="email"
bind:value={_user.email}
placeholder={$i18n.t('Enter Your Email')}
autocomplete="off"
required
/>
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
type="email"
bind:value={_user.email}
placeholder={$i18n.t('Enter Your Email')}
autocomplete="off"
required
/>
</div>
</div>
</div>
<div class="flex flex-col w-full mt-2">
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Password')}</div>
<div class="flex flex-col w-full mt-2">
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Password')}</div>
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
type="password"
bind:value={_user.password}
placeholder={$i18n.t('Enter Your Password')}
autocomplete="off"
/>
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
type="password"
bind:value={_user.password}
placeholder={$i18n.t('Enter Your Password')}
autocomplete="off"
/>
</div>
</div>
</div>
{:else if tab === 'import'}
<div>
<div class="mb-3 w-full">
<input
id="upload-user-csv-input"
hidden
bind:files={inputFiles}
type="file"
accept=".csv"
/>
<button
class="w-full text-sm font-medium py-3 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-800 dark:hover:bg-gray-850 text-center rounded-xl"
type="button"
on:click={() => {
document.getElementById('upload-user-csv-input')?.click();
}}
>
{#if inputFiles}
{inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected.
{:else}
{$i18n.t('Click here to select a csv file.')}
{/if}
</button>
</div>
<div class=" text-xs text-gray-500">
{$i18n.t(
'Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.'
)}
<a
class="underline dark:text-gray-200"
href="{WEBUI_BASE_URL}/static/user-import.csv"
>
Click here to download user import template file.
</a>
</div>
</div>
{/if}
</div>
<div class="flex justify-end pt-3 text-sm font-medium">

View file

@ -154,7 +154,7 @@
if (isCtrlPressed && event.key === '.') {
event.preventDefault();
console.log('openSettings');
document.getElementById('open-settings-button')?.click();
showSettings.set(!$showSettings);
}
// Check if Ctrl + / is pressed