feat: add main.rs, and rest requirements (#2)
* main.rs, docker, diesel database, makefile
* add license
diff --git a/.env b/.env
new file mode 100644
index 0000000..1109c3c
--- /dev/null
+++ b/.env
@@ -0,0 +1,13 @@
+### DB
+DB_USER=postgres
+DB_PASS=postgres
+DB_NAME=casbin
+DB_HOST=casbin-actix-pgsql-db
+DB_PORT=5432
+DATABASE_URL=postgres://postgres:postgresAdmin@127.0.0.1:5432/postgrestest
+
+### Actix
+BIND_ADDRESS=0.0.0.0:1080
+
+### Logger
+RUST_LOG=debug,actix_server=debug
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4f3cb7e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,26 @@
+run:
+ @echo -e '\e[1;31mRunning...\e[0m'
+ @cd docker && docker-compose up --build
+ @echo -e '\e[1;31mDone\e[0m'
+
+start:
+ @echo -e '\e[1;31mStarting...\e[0m'
+ @cd docker && docker-compose start
+ @echo -e '\e[1;31mDone\e[0m'
+
+stop:
+ @echo -e '\e[1;31mStopping...\e[0m'
+ @cd docker && docker-compose stop
+ @echo -e '\e[1;31mDone\e[0m'
+
+destroy:
+ @echo -e '\e[1;31mDestroying...\e[0m'
+ @cd docker && docker-compose down
+ @echo -e '\e[1;31mDone\e[0m'
+
+ssh:
+ @docker exec -ti casbin-actix-pgsql-app /bin/bash
+
+check:
+ @cargo fmt
+ @cargo clippy -- -D warnings
diff --git a/README.md b/README.md
index a81fa2b..98a3623 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,15 @@
-# actix-postgresql-simple
\ No newline at end of file
+# Casbin with Actix & PgSQL
+
+A simple example, using Actix web, diesel-adapter and Postgresql.
+
+## Prerequisite
+
+You need to have `docker` and `docker-compose` commands installed.
+
+## Run
+
+Run `make` to setup and run the application.
+
+Then open http://127.0.0.1:1080/?name=casbin, it should say `OK` which can mean you have access!
+
+If you change the `name` to anything else, it would return a `403` response with `Forbidden` message.
diff --git a/diesel.toml b/diesel.toml
new file mode 100644
index 0000000..92267c8
--- /dev/null
+++ b/diesel.toml
@@ -0,0 +1,5 @@
+# For documentation on how to configure this file,
+# see diesel.rs/guides/configuring-diesel-cli
+
+[print_schema]
+file = "src/schema.rs"
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 0000000..2e56ae6
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,12 @@
+FROM rust:1
+
+RUN cargo install diesel_cli --no-default-features --features postgres
+
+RUN cargo install cargo-watch
+
+WORKDIR /opt/app
+
+VOLUME ["/usr/local/cargo"]
+
+#ENV RUST_BACKTRACE=1
+CMD ["sh", "-c", "diesel database setup --database-url postgres://postgres:postgres@casbin-actix-pgsql-db:5432/casbin && cargo watch -w src -w Cargo.toml -w .env -d 2 -x run"]
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
new file mode 100644
index 0000000..66ae696
--- /dev/null
+++ b/docker/docker-compose.yml
@@ -0,0 +1,23 @@
+version: "3.7"
+
+services:
+ casbin-actix-pgsql-app:
+ container_name: casbin-actix-pgsql-app
+ build: .
+ ports:
+ - 127.0.0.1:1080:1080
+ volumes:
+ - ..:/opt/app
+ links:
+ - casbin-actix-pgsql-db
+ depends_on:
+ - casbin-actix-pgsql-db
+
+ casbin-actix-pgsql-db:
+ container_name: casbin-actix-pgsql-db
+ image: postgres:latest
+ environment:
+ POSTGRES_PASSWORD: "postgres"
+ # To have persistent data, enable this
+ # volumes:
+ # - ../data/pg:/var/lib/postgresql/data
diff --git a/migrations/.gitkeep b/migrations/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/migrations/.gitkeep
diff --git a/migrations/00000000000000_diesel_initial_setup/down.sql b/migrations/00000000000000_diesel_initial_setup/down.sql
new file mode 100644
index 0000000..a9f5260
--- /dev/null
+++ b/migrations/00000000000000_diesel_initial_setup/down.sql
@@ -0,0 +1,6 @@
+-- This file was automatically created by Diesel to setup helper functions
+-- and other internal bookkeeping. This file is safe to edit, any future
+-- changes will be added to existing projects as new migrations.
+
+DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
+DROP FUNCTION IF EXISTS diesel_set_updated_at();
diff --git a/migrations/00000000000000_diesel_initial_setup/up.sql b/migrations/00000000000000_diesel_initial_setup/up.sql
new file mode 100644
index 0000000..d68895b
--- /dev/null
+++ b/migrations/00000000000000_diesel_initial_setup/up.sql
@@ -0,0 +1,36 @@
+-- This file was automatically created by Diesel to setup helper functions
+-- and other internal bookkeeping. This file is safe to edit, any future
+-- changes will be added to existing projects as new migrations.
+
+
+
+
+-- Sets up a trigger for the given table to automatically set a column called
+-- `updated_at` whenever the row is modified (unless `updated_at` was included
+-- in the modified columns)
+--
+-- # Example
+--
+-- ```sql
+-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
+--
+-- SELECT diesel_manage_updated_at('users');
+-- ```
+CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
+BEGIN
+ EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
+ FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
+BEGIN
+ IF (
+ NEW IS DISTINCT FROM OLD AND
+ NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
+ ) THEN
+ NEW.updated_at := current_timestamp;
+ END IF;
+ RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
diff --git a/src/main.rs b/src/main.rs
index a046616..e5adcb9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,16 +1,91 @@
-#![allow(proc_macro_derive_resolution_fallback)]
+// Copyright 2022 The casbin Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
-use actix_web::{get, web, App, HttpServer, Responder};
+use actix_web::{middleware, web, App, HttpResponse, HttpServer, Responder};
+use diesel_adapter::casbin::prelude::*;
+use diesel_adapter::DieselAdapter;
+use serde::Deserialize;
+use std::env;
-#[get("/hello")]
-async fn greet(name: web::Path<String>) -> impl Responder {
- format!("This is {}!", name)
+#[derive(Deserialize)]
+pub struct Visitor {
+ name: String,
}
-#[actix_web::main] // or #[tokio::main]
+#[actix_rt::main]
async fn main() -> std::io::Result<()> {
- HttpServer::new(|| App::new().service(greet))
- .bind(("127.0.0.1", 8080))?
+ dotenv::dotenv().ok();
+ env_logger::init();
+
+ let mut enforcer = get_enforcer().await;
+ enforcer
+ .add_policy(
+ vec!["casbin", "index", "read"]
+ .iter()
+ .map(|s| s.to_string())
+ .collect(),
+ )
+ .await
+ .unwrap();
+
+ let app = move || {
+ App::new()
+ .wrap(middleware::Logger::default())
+ .route("/", web::get().to(index))
+ };
+
+ // Start HTTP server
+ let bind_address = env::var("BIND_ADDRESS").expect("BIND_ADDRESS is not set");
+
+ HttpServer::new(app)
+ .bind(&bind_address)
+ .unwrap_or_else(|_| panic!("Cannot bind address to {}", &bind_address))
.run()
.await
}
+
+async fn index(me: web::Query<Visitor>) -> impl Responder {
+ if grant(&me.name, "index", "read").await.is_err() {
+ return HttpResponse::Forbidden().body("Forbidden");
+ };
+
+ HttpResponse::Ok().body("OK")
+}
+
+async fn grant(sub: &str, obj: &str, act: &str) -> Result<()> {
+ let e = get_enforcer().await;
+
+ if let Ok(authorized) = e.enforce((sub, obj, act)) {
+ if authorized {
+ Ok(())
+ } else {
+ Err(()).unwrap()
+ }
+ } else {
+ Err(()).unwrap()
+ }
+}
+
+async fn get_enforcer() -> Enforcer {
+ let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
+ let pool_size: u32 = std::env::var("POOL_SIZE")
+ .ok()
+ .and_then(|s| s.parse().ok())
+ .unwrap_or(8);
+ let m = DefaultModel::from_file("model/rbac_model.conf")
+ .await
+ .unwrap();
+ let a = DieselAdapter::new(database_url, pool_size).unwrap();
+ Enforcer::new(m, a).await.unwrap()
+}