Skip to content

Commit

Permalink
feat: add todo frontend at index route
Browse files Browse the repository at this point in the history
  • Loading branch information
litsynp committed Apr 20, 2024
1 parent 621c319 commit bec8933
Show file tree
Hide file tree
Showing 19 changed files with 2,118 additions and 24 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# build
/target

# frontend
node_modules
assets/main.css

# IDE
.idea/
.vscode/
Expand Down
46 changes: 46 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ utoipa-redoc = { version = "3.0.0", features = ["axum"] }
utoipa-rapidoc = { version = "3.0.0", features = ["axum"] }
clap = { version = "4.5.4", features = ["derive"] }
askama = "0.12.1"
tower = "0.4.13"
tower-http = { version = "0.5.2", features = ["fs"] }
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
PORT=8000

watch\:install:
cargo install cargo-watch
watch:
cargo watch -x run
watch\:uninstall:
cargo uninstall cargo-watch

db-up:
make db-down
docker-compose up -d --remove-orphans
Expand Down
119 changes: 119 additions & 0 deletions assets/scripts/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* Set authentication cookies
*
* @param data {Object} - The data object containing the access token and refresh token
* @param data.accessToken {string} - The access token
* @param data.refreshToken {string} - The refresh token
*/
function setAuthCookies(data) {
setCookie("access_token", data.accessToken);
setCookie("refresh_token", data.refreshToken);
}

function login(event) {
event.preventDefault();
const form = event.target;
const data = {
email: form.email.value,
password: form.password.value,
};
fetch(form.action, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}).then(function (response) {
if (response.ok) {
response.json().then(function (data) {
setAuthCookies(data);
window.location.href = "/";
});
} else {
response.json().then(function (data) {
alert("Failed to login: " + data.errors[0]);
});
}
});
}

/**
* Register a new user
*
* @param event {Event} - The event object
* @param event.target {Element} - The form element
* @param event.target.email {string} - The email address
* @param event.target.nickname {string} - The nickname
*/
function register(event) {
event.preventDefault();

const form = event.target;
const data = {
email: form.email.value,
nickname: form.nickname.value,
password: form.password.value,
};

fetch(form.action, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}).then(function (response) {
if (response.ok) {
window.location.href = "/login";
} else {
response.json().then(function (data) {
alert(data.message);
});
}
});
}

/**
* Check if the user is authenticated
*
* @returns {boolean} - True if the user is authenticated, false otherwise
*/
function isAuth() {
return document.cookie.indexOf("access_token") !== -1;
}

/**
* Get the authentication token
*
* @returns {null|string} - The authentication token or null if not found
*/
function getAuth() {
const cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].split("=");
if (cookie[0].trim() === "access_token") {
return cookie[1];
}
}
return null;
}

function removeAuthCookies() {
const cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].split("=");
if (
cookie[0].trim() === "access_token" ||
cookie[0].trim() === "refresh_token"
) {
removeCookie(cookie[0].trim());
}
}
}

/**
* Logout the user
*/
function logout() {
removeAuthCookies();
window.location.href = "/";
}
18 changes: 18 additions & 0 deletions assets/scripts/cookies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Get cookie by name
*
* @param cname {string} - The cookie name
* @param cvalue {string} - The cookie value
*/
function setCookie(cname, cvalue) {
document.cookie = `${cname}=${cvalue};path=/`;
}

/**
* Remove cookie by name
*
* @param cname {string} - The cookie name to remove
*/
function removeCookie(cname) {
document.cookie = cname + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
}
96 changes: 96 additions & 0 deletions assets/scripts/todos.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* @typedef PaginatedView
* @template T
* @type {object}
* @property {T[]} items
* @property {number} page
* @property {number} size
*/

/**
* @typedef Todo
* @type {object}
* @property {string} id
* @property {string} title
* @property {string} description
* @property {boolean} completed
* @property {string} createdAt
* @property {string} updatedAt
*/

/**
* Fetch all todos
*
* @param {object} todoRequest
* @param {string} todoRequest.title
* @param {string} todoRequest.description
* @returns {Promise<Todo>} The created todo
*/
function createTodo(todoRequest) {
const { title, description } = todoRequest;

return fetch("/api/todos", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getAuth()}`,
},
body: JSON.stringify({ title, description }),
}).then((response) => response.json());
}

/**
* Fetch all todos
*
* @param {object} [findTodosRequest]
* @param {number} findTodosRequest.page
* @param {number} findTodosRequest.size
* @returns {Promise<PaginatedView<Todo>>} The paginated todos
*/
function findTodos(findTodosRequest) {
const { page = 1, size = 10 } = findTodosRequest || {};

return fetch("/api/todos?page=" + page + "&size=" + size, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getAuth()}`,
},
}).then((response) => response.json());
}

/**
* Edit todo by id
*
* @param {number} todoId
* @param {object} editTodoRequest
* @param {string} editTodoRequest.title
* @param {string} editTodoRequest.description
* @param {boolean} editTodoRequest.completed
* @returns {Promise<Todo>} The updated todo
*/
function editTodo(todoId, editTodoRequest) {
return fetch(`/api/todos/${todoId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getAuth()}`,
},
body: JSON.stringify({ ...editTodoRequest }),
}).then((response) => response.json());
}

/**
* Delete todo by id
*
* @param {number} todoId
* @returns {Promise<void>} The deleted todo
*/
function deleteTodo(todoId) {
return fetch(`/api/todos/${todoId}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getAuth()}`,
},
});
}
Loading

0 comments on commit bec8933

Please sign in to comment.