backup
This commit is contained in:
parent
b04fcd6204
commit
5eead5fd3c
7 changed files with 260 additions and 6 deletions
|
@ -1,7 +1,7 @@
|
|||
use axum::{response::Redirect, Form};
|
||||
use axum::{extract::Path, response::Redirect, Form};
|
||||
use axum_login::AuthSession;
|
||||
|
||||
use crate::{models::ModelPermission, prelude::*};
|
||||
use crate::{handler::internal_server_error, models::{Document, ModelPermission, ModelType, Permission}, prelude::*};
|
||||
|
||||
pub async fn documents_page(
|
||||
State(ctx): State<Context>,
|
||||
|
@ -24,3 +24,103 @@ async fn render_documents_page(ctx: Context, user: crate::entity::user::Model) -
|
|||
|
||||
ctx.render_resp("documents/list_documents.html", values)
|
||||
}
|
||||
|
||||
pub async fn create_document_page(
|
||||
State(ctx): State<Context>,
|
||||
auth_session: AuthSession<Context>,
|
||||
) -> Response {
|
||||
let user = match auth_session.user {
|
||||
Some(user) => user,
|
||||
None => return Redirect::to("/login").into_response(),
|
||||
};
|
||||
|
||||
let projects = ModelPermission::user_projects(&ctx.kv_handles, user.id).unwrap_or_default();
|
||||
|
||||
let values = context! {
|
||||
user => user,
|
||||
projects => projects,
|
||||
};
|
||||
ctx.render_resp("documents/create_document.html", values)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateDocumentSubmission {
|
||||
project_id: Uuid,
|
||||
title: String,
|
||||
}
|
||||
|
||||
pub async fn create_document_submit(
|
||||
State(ctx): State<Context>,
|
||||
auth_session: AuthSession<Context>,
|
||||
form: Form<CreateDocumentSubmission>,
|
||||
) -> Response {
|
||||
let user = match auth_session.user {
|
||||
Some(user) => user,
|
||||
None => return Redirect::to("/login").into_response(),
|
||||
};
|
||||
|
||||
let project = match ModelPermission::user_project(&ctx.kv_handles, user.id, form.project_id) {
|
||||
Ok(Some(project)) => project,
|
||||
Ok(None) => return Redirect::to("/documents/create").into_response(),
|
||||
Err(err) => {
|
||||
error!(?err, "failed to access kv store");
|
||||
return Redirect::to("/documents/create").into_response();
|
||||
}
|
||||
};
|
||||
|
||||
let document = Document {
|
||||
id: Uuid::now_v7(),
|
||||
project_id: project.id,
|
||||
title: form.title.to_owned(),
|
||||
content: "".to_owned(),
|
||||
};
|
||||
|
||||
if let Err(err) = document.save(&ctx.kv_handles) {
|
||||
error!(?err, "failed to save document");
|
||||
return internal_server_error();
|
||||
}
|
||||
info!(?document, "document created");
|
||||
|
||||
let permission = ModelPermission {
|
||||
user_id: user.id,
|
||||
model_type: ModelType::Document,
|
||||
role: Permission::Admin,
|
||||
model_id: document.id,
|
||||
};
|
||||
|
||||
if let Err(err) = permission.add(&ctx.kv_handles) {
|
||||
error!(?err, "failed to save new project permission");
|
||||
return internal_server_error();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Redirect::to("/documents").into_response()
|
||||
}
|
||||
|
||||
pub async fn edit_document_page(
|
||||
State(ctx): State<Context>,
|
||||
auth_session: AuthSession<Context>,
|
||||
Path((id,)): Path<(Uuid,)>,
|
||||
) -> Response {
|
||||
let user = match auth_session.user {
|
||||
Some(user) => user,
|
||||
None => return Redirect::to("/login").into_response(),
|
||||
};
|
||||
|
||||
let document = match ModelPermission::user_document(&ctx.kv_handles, user.id, id) {
|
||||
Ok(Some(document)) => document,
|
||||
Ok(None) => return Redirect::to("/documents").into_response(),
|
||||
Err(err) => {
|
||||
error!(?err, "failed to load document");
|
||||
return internal_server_error();
|
||||
}
|
||||
};
|
||||
|
||||
let values = context! {
|
||||
user => user,
|
||||
document => document,
|
||||
};
|
||||
|
||||
ctx.render_resp("documents/edit_document.html", values)
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ pub async fn create_project_submit(
|
|||
};
|
||||
|
||||
let project = Project {
|
||||
id: Uuid::new_v4(),
|
||||
id: Uuid::now_v7(),
|
||||
owner_id: user.id,
|
||||
|
||||
name: form.name.clone(),
|
||||
|
|
|
@ -140,6 +140,20 @@ impl ModelPermission {
|
|||
Ok(projects)
|
||||
}
|
||||
|
||||
pub fn user_project(kv_handle: &KvHandle, user_id: i32, project_id: Uuid) -> Result<Option<Project>> {
|
||||
let key = format!("{}:{}:{}:{}", user_id, ModelType::Project, Permission::Admin, project_id);
|
||||
let value = kv_handle.permissions.get(key)?;
|
||||
|
||||
match value {
|
||||
Some(value) => {
|
||||
let permission: ModelPermission = bincode::deserialize(&value)?;
|
||||
let project = Project::load(kv_handle, permission.model_id)?;
|
||||
Ok(project)
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn user_documents(kv_handle: &KvHandle, user_id: i32) -> Result<Vec<Document>> {
|
||||
let prefix = format!("{}:{}:", user_id, ModelType::Document);
|
||||
|
||||
|
@ -149,13 +163,28 @@ impl ModelPermission {
|
|||
let permission: ModelPermission = bincode::deserialize(&value)?;
|
||||
ids.push(permission.model_id);
|
||||
}
|
||||
dbg!(&ids);
|
||||
|
||||
let documents: Vec<Document> = ids
|
||||
.into_iter()
|
||||
.filter_map(|id| Document::load(kv_handle, id).ok().flatten())
|
||||
.collect();
|
||||
dbg!(&documents);
|
||||
|
||||
Ok(documents)
|
||||
}
|
||||
|
||||
pub fn user_document(kv_handle: &KvHandle, user_id: i32, document_id: Uuid) -> Result<Option<Document>> {
|
||||
let key = format!("{}:{}:{}:{}", user_id, ModelType::Document, Permission::Admin, document_id);
|
||||
let value = kv_handle.permissions.get(key)?;
|
||||
|
||||
match value {
|
||||
Some(value) => {
|
||||
let permission: ModelPermission = bincode::deserialize(&value)?;
|
||||
let document = Document::load(kv_handle, permission.model_id)?;
|
||||
Ok(document)
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use tower_sessions::SessionManagerLayer;
|
|||
use tower_sessions_sqlx_store::{sqlx::SqlitePool, SqliteStore};
|
||||
use tracing::Level;
|
||||
|
||||
use crate::{config::CommandLineOptions, context::Context, handler::{documents::documents_page, home::home_page, login::logout, login_page, login_submit, projects::{create_project_page, create_project_submit, projects_page}}, kv::KvHandle, logging::setup_logging, templates::make_template_loader};
|
||||
use crate::{config::CommandLineOptions, context::Context, handler::{documents::{create_document_page, create_document_submit, documents_page, edit_document_page}, home::home_page, login::logout, login_page, login_submit, projects::{create_project_page, create_project_submit, projects_page}}, kv::KvHandle, logging::setup_logging, templates::make_template_loader};
|
||||
|
||||
pub async fn run() -> Result<()> {
|
||||
dotenvy::dotenv()?;
|
||||
|
@ -47,6 +47,9 @@ pub async fn run() -> Result<()> {
|
|||
.route("/projects/new", get(create_project_page))
|
||||
.route("/projects/new", post(create_project_submit))
|
||||
.route("/documents", get(documents_page))
|
||||
.route("/documents/new", get(create_document_page))
|
||||
.route("/documents/new", post(create_document_submit))
|
||||
.route("/documents/edit/:id", get(edit_document_page))
|
||||
.layer(trace_layer)
|
||||
.layer(session_layer)
|
||||
.layer(auth_layer)
|
||||
|
|
|
@ -1132,6 +1132,40 @@ select {
|
|||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.select {
|
||||
display: inline-flex;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
height: 3rem;
|
||||
min-height: 3rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 2.5rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
line-height: 2;
|
||||
border-radius: var(--rounded-btn, 0.5rem);
|
||||
border-width: 1px;
|
||||
border-color: transparent;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
|
||||
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
|
||||
linear-gradient(135deg, currentColor 50%, transparent 50%);
|
||||
background-position: calc(100% - 20px) calc(1px + 50%),
|
||||
calc(100% - 16.1px) calc(1px + 50%);
|
||||
background-size: 4px 4px,
|
||||
4px 4px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.select[multiple] {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.btm-nav > * .label {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
|
@ -1519,6 +1553,50 @@ select {
|
|||
}
|
||||
}
|
||||
|
||||
.select:focus {
|
||||
box-shadow: none;
|
||||
border-color: var(--fallback-bc,oklch(var(--bc)/0.2));
|
||||
outline-style: solid;
|
||||
outline-width: 2px;
|
||||
outline-offset: 2px;
|
||||
outline-color: var(--fallback-bc,oklch(var(--bc)/0.2));
|
||||
}
|
||||
|
||||
.select-disabled,
|
||||
.select:disabled,
|
||||
.select[disabled] {
|
||||
cursor: not-allowed;
|
||||
--tw-border-opacity: 1;
|
||||
border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
|
||||
color: var(--fallback-bc,oklch(var(--bc)/0.4));
|
||||
}
|
||||
|
||||
.select-disabled::-moz-placeholder, .select:disabled::-moz-placeholder, .select[disabled]::-moz-placeholder {
|
||||
color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
|
||||
--tw-placeholder-opacity: 0.2;
|
||||
}
|
||||
|
||||
.select-disabled::placeholder,
|
||||
.select:disabled::placeholder,
|
||||
.select[disabled]::placeholder {
|
||||
color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
|
||||
--tw-placeholder-opacity: 0.2;
|
||||
}
|
||||
|
||||
.select-multiple,
|
||||
.select[multiple],
|
||||
.select[size].select:not([size="1"]) {
|
||||
background-image: none;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
[dir="rtl"] .select {
|
||||
background-position: calc(0% + 12px) calc(1px + 50%),
|
||||
calc(0% + 16px) calc(1px + 50%);
|
||||
}
|
||||
|
||||
@keyframes skeleton {
|
||||
from {
|
||||
background-position: 150%;
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-full bg-white">
|
||||
{% include "head.html" %}
|
||||
<body class="h-full">
|
||||
|
||||
<div class="h-full">
|
||||
{% set current_page = "projects" %}
|
||||
{% include "components/sidebar.html" %}
|
||||
|
||||
<main class="pl-72 bg-gray-50 h-full">
|
||||
<div class="navbar bg-accent text-accent-content">
|
||||
<div class="navbar-start">
|
||||
<a class="btn" href="/projects">
|
||||
{{ "arrow-left"|heroicon("w-6 h-6")|safe }} Back
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form action="/documents/new" method="POST">
|
||||
<div class="px-8 py-8 flex flex-col gap-y-4">
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
Title
|
||||
<input type="text" id="title" name="title" class="grow" placeholder="The title of the document is..." />
|
||||
</label>
|
||||
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
Project
|
||||
<select id="project_id" name="project_id" class="grow">
|
||||
{% for project in projects %}
|
||||
<option value="{{ project.id }}">{{ project.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<button class="btn btn-primary">Create</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</main>
|
||||
|
||||
|
||||
<script type="text/javascript" src="/static/main.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -22,14 +22,13 @@
|
|||
</div>
|
||||
<div class="px-8 py-8 flex flex-col gap-y-4">
|
||||
|
||||
<div id="editor" class="prose bg-white"></div>
|
||||
|
||||
{% for document in documents %}
|
||||
<div class="card w-96 bg-base-100 shadow-md border-2 border-solid">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{{ document.name }}</h2>
|
||||
<div class="card-actions justify-end">
|
||||
<button class="btn btn-primary">Open</button>
|
||||
<a href="/documents/edit/{{ document.id }}" class="btn btn-primary">Open</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -37,6 +36,7 @@
|
|||
{% endfor %}
|
||||
|
||||
</div>
|
||||
<div id="editor" class="prose bg-white"></div>
|
||||
</main>
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue