Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redis plugin #366

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod todos;
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// use redis::Commands;
// use redis::Connection;

use redis::Client;
use redis::Commands;
use redis::Connection;
use redis::JsonCommands;
use redis::RedisError;
use redis::RedisResult;
use redis::ToRedisArgs;

use redis_macros::{FromRedisValue, ToRedisArgs};
use serde::{Deserialize, Serialize};

use std::cell::RefCell;

use serde_json::json;

#[derive(Debug, Serialize, Deserialize, Clone, FromRedisValue, ToRedisArgs)]
pub struct Todo {
pub text: String,
pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
}

pub struct RedisDB {
pub conn: RefCell<Connection>,
}

impl RedisDB {
pub fn init() -> Result<RedisDB, redis::RedisError> {
// "redis://127.0.0.1:6379"

let client = redis::Client::open(Self::connection_url())?;
Ok(RedisDB {
conn: RefCell::new(client.get_connection()?),
})
}

pub fn connection_url() -> String {
std::env::var("REDIS_URL").expect("REDIS_URL environment variable expected.")
}

pub fn create(&mut self, item: &Todo) -> Result<isize, redis::RedisError> {
let mut con = self.conn.borrow_mut();
// throw away the result, just make sure it does not fail
println!("1111{:?}", &json!(item).to_string());
let res = con.lpush("todo_list", &json!(item).to_string())?;

Ok(res)
}

pub fn getList(&mut self) -> Result<Vec<Todo>, redis::RedisError> {
let mut con = self.conn.borrow_mut();
// throw away the result, just make sure it does not fail
let res_list: Vec<Todo> = con.lrange("todo_list", 0, 10)?;
Ok(res_list)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react'
import reactLogo from '../images/logo.svg'
import rustLogo from '../images/logo2.svg'
import plus from '../images/plus.svg'

export const Home = () => {
return (
<div>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<img src={rustLogo} className="App-logo" alt="rust-logo" />
<img src={plus} alt="plus" />
<img src={reactLogo} className="App-logo" alt="react-logo" />
</div>
<p>
Edit <code>frontend/src/App.tsx</code> and save to reload.
</p>

<div style={{ display: 'flex', justifyContent: 'center' }}>
<a
className="App-link"
href="https://create-rust-app.dev"
target="_blank"
rel="noopener noreferrer"
>
Docs
</a>
&nbsp;
<a
className="App-link"
href="https://github.com/Wulf/create-rust-app"
target="_blank"
rel="noopener noreferrer"
>
Repo
</a>
</div>
</div>
)
}
205 changes: 205 additions & 0 deletions create-rust-app_cli/template-plugin-redis/frontend/containers/Todo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import React, { useEffect, useState } from "react";

const TodoAPI = {
get: async (page: number, size: number) =>
await (await fetch(`/api/todos?page=${page}&page_size=${size}`)).json(),

getRedis: async (page: number = 1, size: number = 10) =>
await (
await fetch(`/api/redis_todos?page=${page}&page_size=${size}`)
).json(),
create: async (todo: string) =>
await (
await fetch("/api/todos", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ text: todo }),
})
).json(),
createRedis: async (todo: string) =>
await (
await fetch("/api/redis_todos", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
text: todo,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}),
})
).json(),
delete: async (id: number) =>
await fetch(`/api/todos/${id}`, { method: "DELETE" }),
update: async (id: number, todo: string) =>
await fetch(`/api/todos/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ text: todo }),
}),
};

export const Todos = () => {
const [text, setText] = useState<string>("");
const [selectedTodo, editTodo] = useState<Todo | null>(null);
const [todos, setTodos] = useState<PaginationResult<Todo>>();
const [createdTodo, setCreatedTodo] = useState<Todo>();
const pageSize = 5;
const [page, setPage] = useState<number>(0);
const [numPages, setPages] = useState<number>(1);
const [processing, setProcessing] = useState<boolean>(false);

const createTodo = async (todo: string) => {
setProcessing(true);
let createdTodo = await TodoAPI.createRedis(todo);
let todos = await TodoAPI.get(page, pageSize);
await TodoAPI.getRedis(page, pageSize);
setTodos(todos);
setCreatedTodo(createdTodo);
setText("");
setProcessing(false);
};

const updateTodo = async (todo: Todo) => {
setProcessing(true);
await TodoAPI.update(todo.id, text);
setTodos(await TodoAPI.get(page, pageSize));
setText("");
editTodo(null);
setProcessing(false);
};

const deleteTodo = async (todo: Todo) => {
setProcessing(true);
await TodoAPI.delete(todo.id);
setTodos(await TodoAPI.get(page, pageSize));
setProcessing(false);
};

useEffect(() => {
TodoAPI.getRedis();
}, []);

useEffect(() => {
setText(selectedTodo?.text || "");
}, [selectedTodo]);

// fetch on page change
useEffect(() => {
setProcessing(true);
TodoAPI.get(page, pageSize).then((todos) => {
setTodos(todos);
setProcessing(false);
});
}, [page]);

// update total number of pages
useEffect(() => {
if (todos) setPages(todos?.num_pages);
}, [todos]);

useEffect(() => {
editTodo(null);
if (page < 0) setPage(0);
if (numPages != 0 && page >= numPages) setPage(numPages - 1);
}, [page, numPages]);

useEffect(() => {
// go to the latest page when a new todo is created
setPage(numPages - 1);
setCreatedTodo(undefined);
}, [createdTodo]);

return (
<div style={{ display: "flex", flexFlow: "column", textAlign: "left" }}>
<h1>Todos</h1>
{(!todos || todos.total_items === 0) && "No todos, create one!"}
{todos?.items.map((todo) =>
todo.id === selectedTodo?.id ? (
<div key="form" className="Form">
<div style={{ display: "flex" }}>
<input
style={{ flex: 1 }}
value={text}
onChange={(e) => setText(e.target.value)}
/>
<button
disabled={processing}
style={{ height: "40px" }}
onClick={() => updateTodo(todo)}
>
Save
</button>
<button
disabled={processing}
style={{ height: "40px" }}
onClick={() => editTodo(null)}
>
Cancel
</button>
</div>
</div>
) : (
<div className="Form">
<div style={{ flex: 1 }}>
#{todo.id} {todo.text}
</div>
<div>
<a href="#" className="App-link" onClick={() => editTodo(todo)}>
edit
</a>
&nbsp;
<a href="#" className="App-link" onClick={() => deleteTodo(todo)}>
delete
</a>
</div>
</div>
)
)}
{selectedTodo === null && (
<div className="Form">
<div style={{ display: "flex" }}>
<input
style={{ flex: 1 }}
placeholder="New todo..."
value={text}
onChange={(e) => setText(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
createTodo(text);
}
}}
/>
<button
disabled={processing}
style={{ height: "40px" }}
onClick={() => createTodo(text)}
>
Add
</button>
</div>
</div>
)}
<div className="Form">
<div style={{ display: "flex" }}>
<button
disabled={processing || page === 0}
onClick={() => setPage(page - 1)}
>{`<<`}</button>
<span style={{ flex: 1, textAlign: "center" }}>
Page {page + 1} of {numPages}
</span>
<button
disabled={processing || page === numPages - 1}
onClick={() => setPage(page + 1)}
>{`>>`}</button>
</div>
</div>
</div>
);
};
Loading