add: taken slug check

This commit is contained in:
trisua 2025-07-20 20:43:01 -04:00
parent a33ee961fe
commit d80368e6c2
5 changed files with 85 additions and 7 deletions

View file

@ -137,3 +137,34 @@ globalThis.tab_preview = async () => {
document.getElementById("editor_tab_button").classList.add("camo");
document.getElementById("preview_tab_button").classList.remove("camo");
};
let exists_timeout = null;
globalThis.check_exists_input = (e) => {
if (exists_timeout) {
clearTimeout(exists_timeout);
}
exists_timeout = setTimeout(async () => {
if (e.target.value.length < 2 || e.target.value.length > 32) {
e.target.setCustomValidity("");
e.target.removeAttribute("data-invalid");
e.target.reportValidity();
return;
}
const exists = (
await (await fetch(`/api/v1/entries/${e.target.value}`)).json()
).payload;
console.log(exists);
if (exists) {
e.target.setCustomValidity("Slug is already in use");
e.target.setAttribute("data-invalid", "true");
} else {
e.target.setCustomValidity("");
e.target.removeAttribute("data-invalid");
}
e.target.reportValidity();
}, 1000);
};

View file

@ -158,7 +158,7 @@ video {
/* button */
.button {
--h: 35.2px;
--h: 36px;
display: flex;
justify-content: center;
align-items: center;
@ -171,11 +171,11 @@ video {
border: none;
width: max-content;
height: var(--h);
min-height: var(--h);
max-height: var(--h);
line-height: var(--h);
transition: background 0.15s;
text-decoration: none !important;
user-select: none;
appearance: none;
}
.button:hover {
@ -193,17 +193,19 @@ video {
/* input */
input {
--h: 35.2px;
--h: 36px;
padding: var(--pad-2) var(--pad-4);
background: var(--color-raised);
color: var(--color-text);
outline: none;
border: none;
width: max-content;
transition: background 0.15s;
transition:
background 0.15s,
border 0.15s;
height: var(--h);
min-height: var(--h);
max-height: var(--h);
line-height: var(--h);
border-left: solid 0px transparent;
}
input:focus {
@ -212,6 +214,11 @@ input:focus {
background: var(--color-super-raised);
}
input:user-invalid,
input[data-invalid] {
border-left: inset 5px var(--color-red);
}
/* typo */
p {
margin-bottom: var(--pad-4);

View file

@ -46,6 +46,7 @@
("type" "text")
("minlength" "2")
("name" "new_slug")
("oninput" "check_exists_input(event)")
("placeholder" "New url")))
(div
("class" "w-full flex justify-between gap-2")

View file

@ -43,6 +43,7 @@
("minlength" "2")
("maxlength" "32")
("name" "slug")
("oninput" "check_exists_input(event)")
("placeholder" "Custom url"))))
(text "{{ components::footer() }}")

View file

@ -34,6 +34,7 @@ pub fn routes() -> Router {
.route("/api/v1/render", post(render_request))
.route("/api/v1/entries", post(create_request))
.route("/api/v1/entries/{slug}", post(edit_request))
.route("/api/v1/entries/{slug}", get(exists_request))
}
fn default_context(data: &DataClient, build_code: &str) -> Context {
@ -178,6 +179,25 @@ async fn render_request(Json(req): Json<RenderMarkdown>) -> impl IntoResponse {
crate::markdown::render_markdown(&req.content)
}
async fn exists_request(
Extension(data): Extension<State>,
Path(slug): Path<String>,
) -> impl IntoResponse {
let (ref data, _, _) = *data.read().await;
Json(ApiReturn {
ok: true,
message: "Success".to_string(),
payload: data
.query(&SimplifiedQuery {
query: AppDataSelectQuery::KeyIs(format!("entries('{}')", slug)),
mode: AppDataSelectMode::One(0),
})
.await
.is_ok(),
})
}
#[derive(Deserialize)]
struct CreateEntry {
content: String,
@ -206,6 +226,14 @@ async fn create_request(
return Json(Error::DataTooLong("slug".to_string()).into());
}
if req.content.len() < 2 {
return Json(Error::DataTooShort("content".to_string()).into());
}
if req.content.len() > 150_000 {
return Json(Error::DataTooLong("content".to_string()).into());
}
// check for existing
if data
.query(&SimplifiedQuery {
@ -274,6 +302,16 @@ async fn edit_request(
) -> impl IntoResponse {
let (ref data, _, _) = *data.read().await;
// check content length
if req.content.len() < 2 {
return Json(Error::DataTooShort("content".to_string()).into());
}
if req.content.len() > 150_000 {
return Json(Error::DataTooLong("content".to_string()).into());
}
// ...
let (id, mut entry) = match data
.query(&SimplifiedQuery {
query: AppDataSelectQuery::KeyIs(format!("entries('{}')", slug)),