Git Product home page Git Product logo

create-rust-app's People

Contributors

anthonymichaeltdm avatar dependabot[bot] avatar gx0r avatar kat-ms avatar liusen-adalab avatar roblperry avatar sansx avatar vishalsodani avatar wulf avatar xbagon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

create-rust-app's Issues

Print message when server is up (ex: "Listening on http://localhost:3000")

I did create-rust-app and then went into the directory and did cargo run.

At first I got some errors regarding postgres like:
[: FATAL: password authentication failed for user "postgres"](https://stackoverflow.com/questions/74481804/psql-error-connection-to-server-at-localhost-1-port-5432-failed-fatal)

I went into /var/lib/pgsql/data/pg_hba.conf and changed all values with 'ident' to 'trust' (I don't know if its a good practice but went with it just to make the app work) and its fixed

but now when I do cargo run

I'm stuck here

image

What should I do?

Can't run `cargo fullstack` using poem server

I ran create-rust-app myapp and selected sqlite and poem. Setup went smoothly, but when running cargo fullstack I ran into some compiler errors in poem_utils.rs:

   Compiling tera v1.17.1
   Compiling poem-derive v1.3.48
   Compiling lettre_email v0.9.4
   Compiling diesel_migrations v2.0.0
   Compiling poem v1.3.48
   Compiling create-rust-app v8.0.2
error[E0432]: unresolved import `http`
 --> C:\Users\lynn\.cargo\registry\src\github.com-1ecc6299db9ec823\create-rust-app-8.0.2\src\util\poem_utils.rs:1:5
  |
1 | use http::{StatusCode, Uri};
  |     ^^^^ help: a similar path exists: `poem::http`

error[E0433]: failed to resolve: use of undeclared crate or module `tokio`
   --> C:\Users\lynn\.cargo\registry\src\github.com-1ecc6299db9ec823\create-rust-app-8.0.2\src\util\poem_utils.rs:119:16
    |
119 |     let file = tokio::fs::read(path).await.unwrap();
    |                ^^^^^ use of undeclared crate or module `tokio`

Some errors have detailed explanations: E0432, E0433.
For more information about an error, try `rustc --explain E0432`.
error: could not compile `create-rust-app` due to 2 previous errors

I'm using create-rust-app v8.0.2 and poem v1.3.48, and I'm running Windows 10.

> cargo --version
cargo 1.64.0 (387270bc7 2022-09-16)
> rustc --version
rustc 1.64.0 (a55dd71d5 2022-09-19)

Allow CRUD in admin dashboard

The task here is to add CRUD features to the admin dashboard.

Currently, we only read information from the database but don't have means of modifying it.

Creating

Generate forms based on column types. Initially, we can support the following types:

  • boolean,
  • all character types,
  • all numeric types,
  • all temporal types,
  • all json types

We can disable the creation feature for tables which contain unsupported column types.

Updating

Similar to creation, except we should add a WHERE clause that updates the row which matches all column values. Before this, we should check that only 1 column exists for those particular values -- otherwise we won't be able to update only one row for tables without candidate keys.

Deleting

We can easily add deletion based by deleting ONE row where all columns are equivalent to the one that the user wanted to delete.

Adding Rocket

Adding a backend framework

Base requirements for each framework:

  • Add new framework option to CLI, update BackendFramework enum
  • main.rs which starts the server in create-rust-app_cli/template/src/
    • sets up logging
    • adds the database pool and the mailer to app's data (every framework has a way of passing data onto all handlers)
    • sets up /api/todos endpoints (see todo.rs below)
    • (only in production: #[cfg(not(debug_assertions))]) serves files from ./frontend/build with the index.html as the default
    • listens on port 8080
    • returns 404 for all unhandled routes
  • todo.rs which serves the CRUD endpoints for the example 'todo' service in create-rust-app_cli/template/src/services
    • GET /: returns a JSON list of all TODO items
    • GET /id: return a single JSON TODO item
    • POST /: creates and returns a single JSON TODO item
    • PUT /:id: updates and returns a single JSON TODO item
    • DELETE /:id: deletes a single item, returns 200 status code

Optional requirements:

(we can get to these later)

  • Auth plugin suppot
    • Implement all /api/auth routes
    • Add an extractor/guard for auth
  • Storage plugin support
    • Add an example service which shows file uploads (files.rs)

compile error

 Compiling rust_app v0.1.0 (/rust_app)
error: linking with `cc` failed: exit status: 1
= note: /usr/bin/ld: cannot find -lpq: No such file or directory

Generated cargo.toml has out of date dependencies

As said in the title, the dependencies in the generated cargo.toml file are out of date, I'll soon be making a pull request that updates the dsync and tsync versions (as those are the most critical), and I can make another one that updates the rest of the dependencies too if that'd be helpful

Cannot get an additional module to work - [backend] TEMPLATE_FILE

I created another service and model, then wired the frontend. Basically, I made a copy of todo with different data fields/struct (only slightly) diesel creates the database and runs the creates the schema, and no compile errors show in the rust or ts code.... The add button in my app (think the todo add button) stays disabled, so may not be connecting to the database. But in the console this appears when I click the button to navigate to that part of the webapp.

[backend] TEMPLATE_FILE /api/vulns => index.html
[backend] [2022-10-29T10:05:00Z INFO actix_web::middleware::logger] 127.0.0.1 "GET /api/vulns?page=0&page_size=5 HTTP/1.1" 200 646 "http://127.0.0.1:3000/vulns"

Any ideas or help would be appreciated.

Add a Swagger-UI plugin

https://docs.rs/utoipa/latest/utoipa/
https://docs.rs/utoipa-swagger-ui/3.0.1/utoipa_swagger_ui/index.html

these two crates allow developers to create a playground for their RestAPI's, making api's easy to test and play around with.

the documentation itself would look like this: (this example documents the actixweb Auth endpoints)

use actix_http::StatusCode;
use actix_web::cookie::{Cookie, SameSite};
use actix_web::{delete, get, post, web, Error as AWError, Result};
use actix_web::{
    web::{Data, Json, Path, Query},
    HttpRequest, HttpResponse,
};
use serde_json::json;
use utoipa::OpenApi;

use crate::pagination::PaginationParams;
use crate::services::auth::{
    controller,
    controller::{
        ActivationInput, ChangeInput, ForgotInput, LoginInput, RegisterInput, ResetInput,
        COOKIE_NAME,
    },
    Auth, AuthMessageResponse, AuthTokenResponse, UserSessionJson, UserSessionResponse,
};
use crate::services::JwtSecurityAddon;
use create_rust_app::Database;
use create_rust_app::Mailer;

/// handler for GET requests at the .../sessions endpoint,
///
/// requires auth
///
/// queries [`db`](`Database`) for all sessions owned by the User
/// associated with [`auth`](`Auth`)
///
/// breaks up the results of that query as defined by [`info`](`PaginationParams`)
#[utoipa::path(
    context_path = "/api/auth",
    params(PaginationParams),
    responses(
        (status = 200, description = "success, returns a json payload with all the sessions belonging to the authenticated user", body = UserSessionResponse),
        (status = 401, description = "Error: Unauthorized"),
        (status = 500, description = "Could not fetch sessions."),
    ),
    tag = "Sessions",
    security ( ("JWT" = []))
)]
#[get("/sessions")]
async fn sessions(
    db: Data<Database>,
    auth: Auth,
    Query(info): Query<PaginationParams>,
) -> Result<HttpResponse> {
    let result =
        web::block(move || controller::get_sessions(db.into_inner().as_ref(), &auth, &info))
            .await?;

    match result {
        Ok(sessions) => Ok(HttpResponse::Ok().json(sessions)),
        Err((status_code, error_message)) => Ok(HttpResponse::build(
            StatusCode::from_u16(status_code as u16).unwrap(),
        )
        .body(json!({ "message": error_message }).to_string())),
    }
}

/// handler for DELETE requests at the .../sessions/{id} endpoint.
///
/// requires auth
///
/// deletes the entry in the `user_session` with the specified [`item_id`](`ID`) from
/// [`db`](`Database`) if it's owned by the User associated with [`auth`](`Auth`)
#[utoipa::path(
    context_path = "/api/auth",
    responses(
        (status = 200, description = "Deleted", body = AuthMessageResponse),
        (status = 401, description = "User not authenticated"),
        (status = 404, description = "User session could not be found, or does not belong to authenticated user.", body = AuthMessageResponse),
        (status = 500, description = "Internal Error.", body = AuthMessageResponse),
        (status = 500, description = "Could not delete session.", body = AuthMessageResponse),
    ),
    tag = "Sessions",
    security ( ("JWT" = []))
)]
#[delete("/sessions/{id}")]
async fn destroy_session(
    db: Data<Database>,
    item_id: Path<i32>,
    auth: Auth,
) -> Result<HttpResponse> {
    let result =
        web::block(move || controller::destroy_session(&db, &auth, item_id.into_inner())).await?;

    match result {
        Ok(_) => Ok(
            HttpResponse::build(StatusCode::OK).body(json!({"message": "Deleted."}).to_string())
        ),
        Err((status_code, error_message)) => Ok(HttpResponse::build(
            StatusCode::from_u16(status_code as u16).unwrap(),
        )
        .body(json!({ "message": error_message }).to_string())),
    }
}

/// handler for DELETE requests at the .../sessions enpoint
///
/// requires auth
///
/// destroys all entries in the `user_session` table in [`db`](`Database`) owned
/// by the User associated with [`auth`](`Auth`)
#[utoipa::path(
    context_path = "/api/auth",
    responses(
        (status = 200, description = "Deleted", body = AuthMessageResponse),
        (status = 401, description = "User not authenticated"),
        (status = 500, description = "Could not delete sessions.", body = AuthMessageResponse),
    ),
    tag = "Sessions",
    security ( ("JWT" = []))
)]
#[delete("/sessions")]
async fn destroy_sessions(db: Data<Database>, auth: Auth) -> Result<HttpResponse, AWError> {
    let result = web::block(move || controller::destroy_sessions(&db, &auth)).await?;

    match result {
        Ok(_) => Ok(
            HttpResponse::build(StatusCode::OK).body(json!({"message": "Deleted."}).to_string())
        ),
        Err((status_code, error_message)) => Ok(HttpResponse::build(
            StatusCode::from_u16(status_code as u16).unwrap(),
        )
        .body(json!({ "message": error_message }).to_string())),
    }
}

/// handler for POST requests at the .../login endpoint
///
/// creates a user session for the user associated with [`item`](`LoginInput`)
/// in the request body (have the `content-type` header set to `application/json` and content that can be deserialized into [`LoginInput`])
#[utoipa::path(
    context_path = "/api/auth",
    request_body(content = LoginInput, content_type = "application/json"),
    responses(
        (status = 200, description = "Deleted", body = AuthTokenResponse),
        (status = 400, description = "'device' cannot be longer than 256 characters.", body = AuthMessageResponse),
        (status = 400, description = "Account has not been activated.", body = AuthMessageResponse),
        (status = 401, description = "Invalid credentials.", body = AuthMessageResponse),
        (status = 500, description = "An internal server error occurred.", body = AuthMessageResponse),
        (status = 500, description = "Could not create a session.", body = AuthMessageResponse),
    ),
    tag = "Sessions",
)]
#[post("/login")]
async fn login(db: Data<Database>, Json(item): Json<LoginInput>) -> Result<HttpResponse, AWError> {
    let result = web::block(move || controller::login(&db, &item)).await?;

    match result {
        Ok((access_token, refresh_token)) => Ok(HttpResponse::build(StatusCode::OK)
            .cookie(
                Cookie::build(COOKIE_NAME, refresh_token)
                    .secure(true)
                    .http_only(true)
                    .same_site(SameSite::Strict)
                    .finish(),
            )
            .body(json!({ "access_token": access_token }).to_string())),
        Err((status_code, message)) => Ok(HttpResponse::build(
            StatusCode::from_u16(status_code as u16).unwrap(),
        )
        .body(json!({ "message": message }).to_string())),
    }
}

/// handler for POST requests to the .../logout endpount
///
/// If this is successful, delete the cookie storing the refresh token
///
/// TODO: document that it creates a refresh_token
#[utoipa::path(
    context_path = "/api/auth",
    responses(
        (status = 200, description = "deletes the \"refresh_token\" cookie"),
        (status = 401, description = "Invalid session.", body = AuthMessageResponse),
        (status = 401, description = "Could not delete session.", body = AuthMessageResponse),
    ),
    tag = "Sessions",
)]
#[post("/logout")]
async fn logout(db: Data<Database>, req: HttpRequest) -> Result<HttpResponse, AWError> {
    let refresh_token = req
        .cookie(COOKIE_NAME)
        .map(|cookie| String::from(cookie.value()));

    let result =
        web::block(move || controller::logout(&db, refresh_token.as_ref().map(|t| t.as_ref())))
            .await?;

    match result {
        Ok(_) => {
            let mut cookie = Cookie::named(COOKIE_NAME);
            cookie.make_removal();

            Ok(HttpResponse::Ok().cookie(cookie).finish())
        }
        Err((status_code, message)) => Ok(HttpResponse::build(
            StatusCode::from_u16(status_code as u16).unwrap(),
        )
        .body(json!({ "message": message }).to_string())),
    }
}

/// handler for POST requests to the .../refresh endpoint
///
/// refreshes the user session associated with the clients refresh_token cookie
///
/// TODO: document that it needs a refresh_token cookie
#[utoipa::path(
    context_path = "/api/auth",
    responses(
        (status = 200, description = "uses the \"refresh_token\" cookie to give the user a new session", body=AuthTokenResponse),
        (status = 401, description = "Invalid session.", body = AuthMessageResponse),
        (status = 401, description = "Invalid token.", body = AuthMessageResponse),
    ),
    tag = "Sessions",
)]
#[post("/refresh")]
async fn refresh(db: Data<Database>, req: HttpRequest) -> Result<HttpResponse, AWError> {
    let refresh_token = req
        .cookie(COOKIE_NAME)
        .map(|cookie| String::from(cookie.value()));

    let result =
        web::block(move || controller::refresh(&db, refresh_token.as_ref().map(|t| t.as_ref())))
            .await?;

    match result {
        Ok((access_token, refresh_token)) => Ok(HttpResponse::build(StatusCode::OK)
            .cookie(
                Cookie::build(COOKIE_NAME, refresh_token)
                    .secure(true)
                    .http_only(true)
                    .same_site(SameSite::Strict)
                    .finish(),
            )
            .body(json!({ "access_token": access_token }).to_string())),
        Err((status_code, message)) => Ok(HttpResponse::build(
            StatusCode::from_u16(status_code as u16).unwrap(),
        )
        .body(json!({ "message": message }).to_string())),
    }
}

/// handler for POST requests to the .../register endpoint
///
/// creates a new User with the information in [`item`](`RegisterInput`)
///
/// sends an email, using [`mailer`](`Mailer`), to the email address in [`item`](`RegisterInput`)
/// that contains a unique link that allows the recipient to activate the account associated with
/// that email address
#[utoipa::path(
    context_path = "/api/auth",
    request_body(content = RegisterInput, content_type = "application/json"),
    responses(
        (status = 200, description = "Success, sends an email to the user with a link that will let them activate their account", body=AuthMessageResponse),
        (status = 400, description = "Already registered.", body = AuthMessageResponse),
    ),
    tag = "Users",
)]
#[post("/register")]
async fn register(
    db: Data<Database>,
    Json(item): Json<RegisterInput>,
    mailer: Data<Mailer>,
) -> Result<HttpResponse, AWError> {
    let result = controller::register(&db, &item, &mailer);

    match result {
        Ok(()) => Ok(HttpResponse::build(StatusCode::OK)
            .body("{ \"message\": \"Registered! Check your email to activate your account.\" }")),
        Err((status_code, message)) => Ok(HttpResponse::build(
            StatusCode::from_u16(status_code as u16).unwrap(),
        )
        .body(json!({ "message": message }).to_string())),
    }
}

/// handler for GET requests to the .../activate endpoint
///
/// activates the account associated with the token in [`item`](`ActivationInput`)
#[utoipa::path(
    context_path = "/api/auth",
    params(ActivationInput),
    responses(
        (status = 200, description = "Success, account associated with activation_token is activated", body=AuthMessageResponse),
        (status = 200, description = "Already activated.", body = AuthMessageResponse),
        (status = 400, description =  "Invalid token.", body = AuthMessageResponse),
        (status = 401, description =  "Invalid token", body = AuthMessageResponse),
        (status = 500, description =  "Could not activate user. ", body = AuthMessageResponse),
    ),
    tag = "Users",
)]
#[get("/activate")]
async fn activate(
    db: Data<Database>,
    Query(item): Query<ActivationInput>,
    mailer: Data<Mailer>,
) -> Result<HttpResponse, AWError> {
    let result = controller::activate(&db, &item, &mailer);

    match result {
        Ok(()) => Ok(HttpResponse::build(StatusCode::OK).body("{ \"message\": \"Activated!\" }")),
        Err((status_code, message)) => Ok(HttpResponse::build(
            StatusCode::from_u16(status_code as u16).unwrap(),
        )
        .body(json!({ "message": message }).to_string())),
    }
}

/// handler for POST requests to the .../forgot endpoint
///
/// sends an email to the email in the ['ForgotInput'] Json in the request body
/// that will allow the user associated with that email to change their password
///
/// sends an email, using [`mailer`](`Mailer`), to the email address in [`item`](`RegisterInput`)
/// that contains a unique link that allows the recipient to reset the password
/// of the account associated with that email address (or create a new account if there is
/// no accound accosiated with the email address)
#[utoipa::path(
    context_path = "/api/auth",
    request_body(content = ForgotInput, content_type = "application/json"),
    responses(
        (status = 200, description = "Success, password reset email is sent to users email", body=AuthMessageResponse),
    ),
    tag = "Users",
)]
#[post("/forgot")]
async fn forgot_password(
    db: Data<Database>,
    Json(item): Json<ForgotInput>,
    mailer: Data<Mailer>,
) -> Result<HttpResponse, AWError> {
    let result = controller::forgot_password(&db, &item, &mailer);

    match result {
        Ok(()) => Ok(HttpResponse::build(StatusCode::OK)
            .body("{ \"message\": \"Please check your email.\" }")),
        Err((status_code, message)) => Ok(HttpResponse::build(
            StatusCode::from_u16(status_code as u16).unwrap(),
        )
        .body(json!({ "message": message }).to_string())),
    }
}

/// handler for POST requests to the .../change endpoint
///
/// requires auth
///
/// change the password of the User associated with [`auth`](`Auth`)
/// from [`item.old_password`](`ChangeInput`) to [`item.new_password`](`ChangeInput`)
#[utoipa::path(
    context_path = "/api/auth",
    request_body(content = ChangeInput, content_type = "application/json"),
    responses(
        (status = 200, description = "Success, password changed", body=AuthMessageResponse),
        (status = 400, description = "Missing password.", body=AuthMessageResponse),
        (status = 400, description = "The new password must be different.", body=AuthMessageResponse),
        (status = 400, description = "Account has not been activated.", body=AuthMessageResponse),
        (status = 400, description = "Invalid credentials.", body=AuthMessageResponse),
        (status = 500, description = "Could not find user.", body=AuthMessageResponse),
        (status = 500, description = "Could not update password.", body=AuthMessageResponse),
    ),
    tag = "Users",
    security ( ("JWT" = []))
)]
#[post("/change")]
async fn change_password(
    db: Data<Database>,
    Json(item): Json<ChangeInput>,
    auth: Auth,
    mailer: Data<Mailer>,
) -> Result<HttpResponse, AWError> {
    let result = controller::change_password(&db, &item, &auth, &mailer);

    match result {
        Ok(()) => Ok(HttpResponse::build(StatusCode::OK)
            .body(json!({"message": "Password changed."}).to_string())),
        Err((status_code, message)) => Ok(HttpResponse::build(
            StatusCode::from_u16(status_code as u16).unwrap(),
        )
        .body(json!({ "message": message }).to_string())),
    }
}

/// handler for POST requests to the .../check endpoint
///
/// requires auth, but doesn't match it to a user
///
/// see [`controller::check`]
///
/// # Responses
/// | StatusCode | content |
/// |:------------|---------|
/// | 200 | ()
#[utoipa::path(
    context_path = "/api/auth",
    responses(
        (status = 200, description = "Success"),
    ),
    tag = "Users",
    security ( ("JWT" = []))
)]
#[post("/check")]
async fn check(auth: Auth) -> HttpResponse {
    controller::check(&auth);
    HttpResponse::Ok().finish()
}

/// handler for POST requests to the .../reset endpoint
///
/// changes the password of the user associated with [`item.reset_token`](`ResetInput`)
/// to [`item.new_password`](`ResetInput`)
#[utoipa::path(
    context_path = "/api/auth",
    request_body(content = ResetInput, content_type = "application/json"),
    responses(
        (status = 200, description = "Password changed.", body=AuthMessageResponse),
        (status = 400, description = "Invalid token.", body=AuthMessageResponse),
        (status = 400, description = "Account has not been activated.", body=AuthMessageResponse),
        (status = 400, description = "The new password must be different.", body=AuthMessageResponse),
        (status = 401, description = "Invalid token.", body=AuthMessageResponse),
        (status = 500, description = "Could not update password.", body=AuthMessageResponse),
    ),
    tag = "Users",
)]
#[post("/reset")]
async fn reset_password(
    db: Data<Database>,
    Json(item): Json<ResetInput>,
    mailer: Data<Mailer>,
) -> Result<HttpResponse, AWError> {
    let result = controller::reset_password(&db, &item, &mailer);

    match result {
        Ok(()) => Ok(HttpResponse::build(StatusCode::OK)
            .body(json!({"message": "Password reset"}).to_string())),
        Err((status_code, message)) => Ok(HttpResponse::build(
            StatusCode::from_u16(status_code as u16).unwrap(),
        )
        .body(json!({ "message": message }).to_string())),
    }
}

/// returns the endpoints for the Auth service
pub fn endpoints(scope: actix_web::Scope) -> actix_web::Scope {
    scope
        .service(sessions)
        .service(destroy_session)
        .service(destroy_sessions)
        .service(login)
        .service(logout)
        .service(check)
        .service(refresh)
        .service(register)
        .service(activate)
        .service(forgot_password)
        .service(change_password)
        .service(reset_password)
}

// swagger
#[derive(OpenApi)]
#[openapi(
    paths(sessions, destroy_session, destroy_sessions, login, logout, refresh, register, activate, forgot_password, change_password, check, reset_password),
    components(
        schemas(UserSessionResponse, UserSessionJson, AuthMessageResponse, AuthTokenResponse, LoginInput, RegisterInput, ForgotInput, ChangeInput, ResetInput)
    ),
    tags(
        (name = "Auth", description = "users and user_sessions management endpoints"),
        (name = "Sessions", description = "Endpoints for user_sessions management"),
        (name = "Users", description = "Endpoints for useres management"),
    ),
    modifiers(&JwtSecurityAddon)
)]
pub struct ApiDoc;

and serving the ui involves adding

            use utoipa::OpenApi;
            use utoipa_swagger_ui::{SwaggerUi, Url};
            app = app.service(SwaggerUi::new("/swagger-ui/{_:.*}").urls(vec![
                (
                    Url::new("auth", "/api-doc/openapi_auth.json"),
                    services::auth::ApiDoc::openapi(),
                ),
                ...
            ]));

to the main #[cfg(debug_assertions)] block in a projects main.rs file

the resulting UI looks something like this

image

the UI also shows all the documented responses and requests, the format of said requests, and even allows you to directly interact with the API with the "Try it out" button:
image

WASM?

Just saw this project and thought that it would be cool if there was an option to generate a Rust WASM project that interfaces with the React app? So React is only UI and all the logic is in the WASM app and it can share code with the backend?

Diesel 2

We should update the project to support diesel 2

Not found error for a recovery url

Hi,

I followed the steps to install the app and started a project. My steps -

  1. create-rust-app bookmarks
  2. selected poem as a backend
  3. deselected Container Plugin: dockerize your app and Storage Plugin: adds S3 file storage capabilities
  4. updated env and ran cargo fullstack

I was able to register. But, the activation link http://localhost:8080/activate?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTI1MzQ0MjYsInN1YiI6MSwidG9rZW5fdHlwZSI6ImFjdGl2YXRpb25fdG9rZW4ifQ.-CMs1vftV31TRVFd1ZotDU5yWpRlR2DbUJGvMg9eN2o' when pasted in browser gave not found` response. So, I changed the port to 3000 and pasted the token in the input box.
Then, I clicked on forgot password. An email was dispatched and printed on the console. I copied the link from the console. My link was

http://localhost:8080/reset?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTAwMjgyNjUsInN1YiI6MSwidG9rZW5fdHlwZSI6InJlc2V0X3Rva2VuIn0.QhSunPvvFm2QVmK38_KLZJW0X7QxtN-1_jWwzO9ovyo

I clicked the link and response was not found

So, I tried

http://127.0.0.1:3000/reset?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTAwMjgyNjUsInN1YiI6MSwidG9rZW5fdHlwZSI6InJlc2V0X3Rva2VuIn0.QhSunPvvFm2QVmK38_KLZJW0X7QxtN-1_jWwzO9ovyo. It worked!

So, how to fix this issue?

Adding Salvo

Adding a backend framework

Base requirements for each framework:

  • Add new framework option to CLI, update BackendFramework enum
  • main.rs which starts the server in create-rust-app_cli/template/src/
    • sets up logging
    • adds the database pool and the mailer to app's data (every framework has a way of passing data onto all handlers)
    • sets up /api/todos endpoints (see todo.rs below)
    • (only in production: #[cfg(not(debug_assertions))]) serves files from ./frontend/build with the index.html as the default
    • listens on port 8080
    • returns 404 for all unhandled routes
  • todo.rs which serves the CRUD endpoints for the example 'todo' service in create-rust-app_cli/template/src/services
    • GET /: returns a JSON list of all TODO items
    • GET /id: return a single JSON TODO item
    • POST /: creates and returns a single JSON TODO item
    • PUT /:id: updates and returns a single JSON TODO item
    • DELETE /:id: deletes a single item, returns 200 status code

Optional requirements:

(we can get to these later)

  • Auth plugin suppot
    • Implement all /api/auth routes
    • Add an extractor/guard for auth
  • Storage plugin support
    • Add an example service which shows file uploads (files.rs)

`cargo fullstack` issues on Windows

After following the instruction to the point where I used cargo fullstack I encountered this:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error { kind: NotFound, message: "program not found" }', .cargo/bin/fullstack.rs:17:10

I found out the problem probably has to do something with this:

〉which yarn
╭───┬──────┬──────────────────────────────────┬──────────╮
│ # │ arg  │               path               │ built-in │
├───┼──────┼──────────────────────────────────┼──────────┤
│ 0 │ yarn │ C:\Program Files\nodejs\yarn.CMD │ false    │
╰───┴──────┴──────────────────────────────────┴──────────╯

After some research I found some fitting issues..
#issuecomment-368300622
rust-lang/rust#94743

For now the only idea I have to fix this, would be some Windows specific code to check for .CMD or maybe use which, which should support that?

[BUG] Hard-coded URL's should be set by environment variable(s)

let link = &format!(
"http://localhost:3000/reset?token={reset_token}",
reset_token = reset_token
);
mail::auth_recover_existent_account::send(&mailer, &user.email, link);
} else {
let link = &format!("http://localhost:300/register");
mail::auth_recover_nonexistent_account::send(&mailer, &item.email, link);
}

As said in the subject, the hard-coded base URL ("http://localhost:3000/") should be set by an environment variable (maybe PUBLIC_BASE_URL or something like that) because in a production environment the recipient of the email, an end-user, will not be running the web server on their local machine, the server will (probably) be hosted on a cloud service provider like Amazon AWS, Linode, etc. and that the user connects to through a browser.

I haven't checked the whole codebase yet, but almost all references to https://localhost:3000 should instead read from an environment variable.

Also, the second link in the code snippet above would be https://localhost:3000, not https://localhost:300

I want to contribute to this projects

Hey, I did not how to contact you, so I decided to open a Issue. I'd love to contribute to this project because I was looking for something like, I even thought about building one myself, but I found this projects which is quite advance. Maybe we could talk send me a message...

Adding Warp

Adding a backend framework

Base requirements for each framework:

  • Add new framework option to CLI, update BackendFramework enum
  • main.rs which starts the server in create-rust-app_cli/template/src/
    • sets up logging
    • adds the database pool and the mailer to app's data (every framework has a way of passing data onto all handlers)
    • sets up /api/todos endpoints (see todo.rs below)
    • (only in production: #[cfg(not(debug_assertions))]) serves files from ./frontend/build with the index.html as the default
    • listens on port 8080
    • returns 404 for all unhandled routes
  • todo.rs which serves the CRUD endpoints for the example 'todo' service in create-rust-app_cli/template/src/services
    • GET /: returns a JSON list of all TODO items
    • GET /id: return a single JSON TODO item
    • POST /: creates and returns a single JSON TODO item
    • PUT /:id: updates and returns a single JSON TODO item
    • DELETE /:id: deletes a single item, returns 200 status code

Optional requirements:

(we can get to these later)

  • Auth plugin suppot
    • Implement all /api/auth routes
    • Add an extractor/guard for auth
  • Storage plugin support
    • Add an example service which shows file uploads (files.rs)

Automated sql migrations

https://github.com/tvallotton/models

this tool will generate migrations for structs as long as they are annotated with a derive model macro and macros to describe the constraints on the sql fields.
b9cd5595bd3a6127411d9e24db67bde3

this are the migrations generated by diesel
e0c0391772f9649b7eca37941e0f42cc

this is the migration generated by the models_cli tool with the models generate -r command
2e34a5bd1965901d07b6979752b3ea7d

Using chmod on windows

When running this on windows, chmod is used, causing the instalation to fail sometwhat (Some files are still copied).

Endless ""GET /__vite_ping HTTP/1.1" 404

Reproduction

Run command as tutorial video in readme: https://github.com/Wulf/create-rust-app/blob/main/docs/create-rust-app-v2.mp4

Logs

[backend] [2022-10-12T01:47:43Z INFO  actix_web::middleware::logger] 192.168.124.1 "POST /api/auth/refresh HTTP/1.1" 401 49 "http://192.168.124.111:3000/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" 0.002163
[backend] ASSET_FILE /src/images/plus.svg => ./frontend/src/images/plus.svg
[backend] ASSET_FILE /src/images/logo.svg => ./frontend/src/images/logo.svg
[backend] [2022-10-12T01:47:43Z INFO  actix_web::middleware::logger] 192.168.124.1 "GET /src/images/plus.svg HTTP/1.1" 304 0 "http://192.168.124.111:3000/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" 0.000662
[backend] [2022-10-12T01:47:43Z INFO  actix_web::middleware::logger] 192.168.124.1 "GET /src/images/logo.svg HTTP/1.1" 304 0 "http://192.168.124.111:3000/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" 0.000610
[backend] [2022-10-12T01:47:43Z INFO  actix_web::middleware::logger] 192.168.124.1 "GET /api/development/health HTTP/1.1" 200 41 "http://192.168.124.111:3000/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" 0.000834
[backend] [2022-10-12T01:47:43Z INFO  actix_web::middleware::logger] 192.168.124.1 "GET /api/development/db/needs-migration HTTP/1.1" 200 25 "http://192.168.124.111:3000/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" 0.002727
[backend] PUBLIC_FILE /pwa.json => ./frontend/public/pwa.json
[backend] [2022-10-12T01:47:43Z INFO  actix_web::middleware::logger] 192.168.124.1 "GET /pwa.json HTTP/1.1" 304 0 "http://192.168.124.111:3000/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" 0.000776
[backend] The vite dev server seems to be down...
[backend] [2022-10-12T01:47:45Z INFO  actix_web::middleware::logger] 192.168.124.1 "GET /__vite_ping HTTP/1.1" 404 20 "http://192.168.124.111:3000/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" 0.000735
[backend] The vite dev server seems to be down...
[backend] [2022-10-12T01:47:46Z INFO  actix_web::middleware::logger] 192.168.124.1 "GET /__vite_ping HTTP/1.1" 404 20 "http://192.168.124.111:3000/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" 0.000742
[backend] The vite dev server seems to be down...

question

It seems that "vite_ping" is fetched by vite plugin from "localhost:3000",which will derectly go to backend default endpoints. It seems like a bug. Should create-rust-app put index.html in frontend and query "localhost:21012" in browser?

async function waitForSuccessfulPing(ms = 1000) {
  // eslint-disable-next-line no-constant-condition
  while (true) {
    try {
      const pingResponse = await fetch(`${base}__vite_ping`)

      // success - 2xx status code
      if (pingResponse.ok) break
      // failure - non-2xx status code
      else throw new Error()
    } catch (e) {
      // wait ms before attempting to ping again
      await new Promise((resolve) => setTimeout(resolve, ms))
    }
  }
}

switch create-react-app to vite

hey, I noticed that you use create-react-app to develop frontend, do you have interest switch that to vite, not only vite is lightly, but also it has a awesome plugin can compile you web into wasm vite-plugin-rsw

Add Redis plugin

Plugin Requirements

Backend

  • Use redis-rs to connect to a local redis instance
  • Expose Redis as data in all handlers
  • (optional) if the auth plugin is enabled, cache auth checks in redis

Template mailer settings query

Great project, have enjoyed using it - thanks!

I've got a query about some of the mailer settings that are used in the project:

It looks like there's an environment variable, SEND_EMAIL checked here:

let actually_send: bool = env::var("SEND_EMAIL", "false").eq("true");

But further down there is some validation that looks for a different environment variable SEND_MAIL:

if std::env::var("SEND_EMAIL").is_err() || std::env::var("SEND_MAIL").unwrap().eq("true") {

Was it intended for there to be two different settings to control separate behaviours? The check also appears to print an error message (that at face value sounds contradictory) if SEND_MAIL is set to true.

Remove `plugin-auth` dependency for `plugin-dev`

❯ cargo fullstack
    Updating crates.io index
error: failed to select a version for `create-rust-app`.
    ... required by package `my-todo-app v0.1.0 (/home/teresa/dev/rust/my-todo-app)`
versions that meet the requirements `^8.0.0` are: 8.0.0

the package `my-todo-app` depends on `create-rust-app`, with features: `database_sqlite` but `create-rust-app` does not have these features.


failed to select a version for `create-rust-app` which could resolve this conflict

here's the cargo.toml:

[[bin]]
name = "fullstack"
path = ".cargo/bin/fullstack.rs"

[[bin]]
name = "tsync"
path = ".cargo/bin/tsync.rs"

[[bin]]
name = "my-todo-app"
path = "backend/main.rs"

[dependencies]
actix-files = "0.6.0"
actix-http = "3.0.0"
actix-multipart = "0.4.0"
actix-web = "4.0.1"
futures-util = "0.3.21"
create-rust-app = {version="8.0.0", features=["plugin_dev", "plugin_storage", "database_sqlite", "backend_actix-web"]}
serde_json = "1.0.79"
tsync = "1.2.1"

[dependencies.chrono]
features = ["serde"]
version = "0.4.19"

[dependencies.diesel]
default-features = false
features = ["sqlite", "r2d2", "chrono"]
version = "2.0.0-rc.1"

[dependencies.serde]
features = ["derive"]
version = "1.0.133"

[dependencies.tokio]
features = ["full"]
version = "1"

[package]
default-run = "my-todo-app"
edition = "2021"
name = "my-todo-app"
publish = false
version = "0.1.0"
[profile.dev]
debug-assertions = true

Documentation Site

The first step here is to create an outline for documentation sections such. From there, we can create more issues to populate individual sections.

Here are some sections we can't miss:

  • Introduction
  • Installation
  • Getting started
  • Plugins
    • Authentication
    • Development
    • Administration
    • Storage
    • Container
  • Testing
  • Deployment

backend/schema.rs is empty or not present

After running create-rust-app my-todo-app, and choosing postgres, actix-web, and all plugins I get

   Compiling create-rust-app v8.1.0
    Finished dev [unoptimized + debuginfo] target(s) in 1m 06s
     Running `.cargo/.build/debug/dsync`
thread 'main' panicked at '`backend/schema.rs` is empty or not present', .cargo/bin/dsync.rs:42:9

This is on Ubuntu 22.04 running in WSL.

Add GraphQL plugin

Plugin Requirements

Backend

  • query to list notes
  • mutation to create notes
  • expose graphql on POST /api/graphql
  • expose subscriptions on GET /api/graphql/ws
  • expose playground on GET /api/graphql/playground

Frontend

  • setup the apollo client
  • add a notes page which uses the query/mutation

We can use the async-graphql implementation.

release mode can't recognize .env file

cargo run --release
......
thread 'main' panicked at 'No SECRET_KEY environment variable set!', /home/xxx/.cargo/registry/src/github.com-1ecc6299db9ec823/create-rust-app-8.0.2/src/lib.rs:65:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Although the .env does exist and has SECRET_KEY set and working fine with cargo run in dev mode.

Feature Request - Option to have no Database

Thanks for this project, it's super handy.
One option i'd like to see, especially when just learning, is the option to have no Database in the backend and just have the bare minimum to get the server going with the base create-react-app page loaded.

Boileplate frontend fixes

Two small fixes we need to make to the generated front-end:

  • the activation page doesn't pickup the ?token= query parameter.
    • fetch token from url params, introduce a useQueryParam() hook
  • the login request should have "device" set to "web"

redis plugin and more

I love to see Redis, Hadoop, Kafka etc added as plugins
also I'd love to talk about the project and how I could help out on discord

Move admin dashboard to `/admin`

Instead of serving the dashboard at all routes on the development backend, we should serve it on a configurable route like /admin because then users will be able to use it in production environments as well.

Unresolved Import `http`

Received this error on a brand new project created today.

error[E0432]: unresolved import `http`
 --> /Users/username/.cargo/registry/src/github.com-1ecc6299db9ec823/create-rust-app-6.0.1/src/util/actix_web_utils.rs:3:5
  |
3 | use http::StatusCode;
  |     ^^^^ help: a similar path exists: `actix_web::http`

Cargo.toml

[[bin]]
name = "fullstack"
path = ".cargo/bin/fullstack.rs"

[[bin]]
name = "tsync"
path = ".cargo/bin/tsync.rs"

[[bin]]
name = "my-project"
path = "backend/main.rs"

[dependencies]
actix-files = "0.6.0"
actix-http = "3.0.0"
actix-multipart = "0.4.0"
actix-web = "4.0.1"
async-graphql = "3.0.38"
futures-util = "0.3.21"
jsonwebtoken = "8.1.0"
async-graphql-actix-web = "3.0.38"
serde_json = "1.0.79"
tsync = "1.2.1"

[dependencies.chrono]
features = ["serde"]
version = "0.4.19"

[dependencies.create-rust-app]
features = ["plugin_dev", "plugin_auth", "plugin_container", "plugin_graphql", "backend_actix-web"]
version = "6.0.1"

[dependencies.diesel]
default-features = false
features = ["postgres", "r2d2", "chrono"]
version = "1.4.8"

[dependencies.serde]
features = ["derive"]
version = "1.0.133"

[dependencies.tokio]
features = ["full"]
version = "1"

[package]
default-run = "my-project"
edition = "2021"
name = "my-project"
publish = false
version = "0.1.0"
[profile.dev]
debug-assertions = true

cargo --version
cargo 1.60.0 (d1fd9fe2c 2022-03-01

Add RabbitMQ Plugin

Some projects may require queues -- for them, it might help to have lapin setup and connected with a local RabbitMQ instance.

Plugin Requirements

Backend

  • Connect with lapin
  • Create a default queue
  • Add RabbitMQ data so it can be used in handlers to queue messages

Blank starter via `--blank`

Some users prefer not to have the initial template which showcases functionality and plugins. We should add a --blank option to the CLI which generates a project that doesn't have the example "TODO" service, modal, migration, and front-end view.

Related #12

Adding Axum

Adding a backend framework

Base requirements for each framework:

  • Add new framework option to CLI, update BackendFramework enum
  • main.rs which starts the server in create-rust-app_cli/template/src/
    • sets up logging
    • adds the database pool and the mailer to app's data (every framework has a way of passing data onto all handlers)
    • sets up /api/todos endpoints (see todo.rs below)
    • (only in production: #[cfg(not(debug_assertions))]) serves files from ./frontend/build with the index.html as the default
    • listens on port 8080
    • returns 404 for all unhandled routes
  • todo.rs which serves the CRUD endpoints for the example 'todo' service in create-rust-app_cli/template/src/services
    • GET /: returns a JSON list of all TODO items
    • GET /id: return a single JSON TODO item
    • POST /: creates and returns a single JSON TODO item
    • PUT /:id: updates and returns a single JSON TODO item
    • DELETE /:id: deletes a single item, returns 200 status code

Optional requirements:

(we can get to these later)

  • Auth plugin suppot
    • Implement all /api/auth routes
    • Add an extractor/guard for auth
  • Storage plugin support
    • Add an example service which shows file uploads (files.rs)

Schema.rs is empty

The todo.rs within models and services complains that failed to resolve: could not find todos in schema which makes sense as the file is empty.
Is this expected behaviour? Are we meant to build the schema ourselves? :)
This prevents cargo from building.

Compile Error: Use of undeclared crate or module `argonautica`

I followed the quick start to create a new app.

When I run cargo fullstack or cargo build, I receive the following error:

   Compiling create-rust-app v8.1.1
error[E0433]: failed to resolve: use of undeclared crate or module `argonautica`
 --> /Users/rohit/.cargo/registry/src/github.com-1ecc6299db9ec823/create-rust-app-8.1.1/src/dev/endpoints/service_poem.rs:1:5
  |
1 | use argonautica::config::Version::_0x10;
  |     ^^^^^^^^^^^ use of undeclared crate or module `argonautica`

Originates here:

use argonautica::config::Version::_0x10;

[enhancement] Status code for /activate and /register could probably be more specific

return Err((400, "Already registered."));

  • Here, a 409 response code might be more specific, as this only happens when the client is attempting to create a duplicate user.

return Err((200, "Already activated!"));

  • Wouldn't a 303 status code, be more appropriate here, as the client should be redirected to .../login after POSTing to .../activate when their account is already?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.