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()
+}