<repo_name>sunakan/todo-actixweb<file_sep>/Dockerfile
FROM rust:1-slim as builder
WORKDIR /var/local/app
COPY ./Cargo.* ./
RUN mkdir -p ./src && touch ./src/lib.rs
RUN cargo build --release
COPY ./src ./src
RUN cargo build --release

FROM debian:buster-slim
COPY --from=builder /var/local/app/target/release/todo-actixweb /usr/local/bin/todo-actixweb
CMD ["todo-actixweb"]
<file_sep>/Makefile
################################################################################
# 変数
################################################################################
HEROKU_APP_NAME := todo-actixweb

################################################################################
# タスク
################################################################################
.PHONY: heroku-login
heroku-login: ## heroku login
	heroku login --interactive

.PHONY: heroku-create-app
heroku-create-app: ## heroku create ${HEROKU_APP_NAME}
	(heroku apps | grep ${HEROKU_APP_NAME}) || heroku create ${HEROKU_APP_NAME}

.PHONY: heroku-container-login
heroku-container-login: ## heroku container:login
	heroku container:login

.PHONY: heroku-build
heroku-build: ## heroku用のtagを付けたDocker imageをbuild
	docker build . --tag registry.heroku.com/${HEROKU_APP_NAME}/web

.PHONY: heroku-push
heroku-push: ## heroku用のtagを付けたDocker imageをpush
	docker push registry.heroku.com/${HEROKU_APP_NAME}/web

.PHONY: heroku-release
heroku-release: ## heroku で release
	heroku container:release web

.PHONY: docker-build
docker-build: ## docker-compose build
	docker-compose build

.PHONY: up
up: ## docker-compose up
	docker-compose up



.PHONY: bash
bash: ## docker-compose run --rm --service-ports app bash
	docker-compose run --rm --service-ports app bash

.PHONY: chown
chown: ## sudo chown -R ${USER}:${USER} ./
	sudo chown -R ${USER}:${USER} ./

.PHONY: down
down: ## docker-compose down
	docker-compose down

.PHONY: deploy
deploy: ## docker-compose down
	git subtree push --prefix build/ origin gh-pages


################################################################################
# マクロ
################################################################################
# Makefileの中身を抽出してhelpとして1行で出す
# $(1): Makefile名
define help
  grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(1) \
  | grep --invert-match "## non-help" \
  | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
endef

################################################################################
# タスク
################################################################################
.PHONY: help
help: ## Make タスク一覧
	@echo '######################################################################'
	@echo '# Makeタスク一覧'
	@echo '# $$ make XXX'
	@echo '# or'
	@echo '# $$ make XXX --dry-run'
	@echo '######################################################################'
	@echo $(MAKEFILE_LIST) \
	| tr ' ' '\n' \
	| xargs -I {included-makefile} $(call help,{included-makefile})
<file_sep>/src/main.rs
use actix_web::{App, HttpServer, Responder, web};
use std::{env, io};
use serde::{ Serialize, Deserialize };
use actix_web::http::StatusCode;

#[derive(Serialize)]
struct Status {
    status: String,
}

#[derive(Serialize)]
struct GainedTodo {
    id: u32,
    title: String,
    done: bool,
}

#[derive(Deserialize)]
struct TodoBeforeSave {
   title: String,
}

#[derive(Serialize, Deserialize)]
struct SavedTodo {
    id: u32,
    title: String,
    done: bool,
}

#[derive(Serialize)]
struct DeletedTodo {
    id: u32,
}

async fn status() -> impl Responder {
    web::HttpResponse::Ok().json(Status {
        status: "Up".to_string(),
    })
}

async fn todos() -> impl Responder {
    let todos : Vec::<GainedTodo> = vec![];
    web::HttpResponse::Ok().json(todos)
}

async fn create_todo(todo: web::Json<TodoBeforeSave>) -> impl Responder {
    web::HttpResponse::Ok().json(SavedTodo {
        id: 1,
        title: todo.title.to_string(),
        done: false,
    }).with_status(StatusCode::CREATED)
}

async fn get_todo(id: web::Path<u32,>) -> impl Responder {
    web::HttpResponse::Ok().json(SavedTodo {
        id: id.0,
        title: "dummy".to_string(),
        done: false,
    }).with_status(StatusCode::OK)
}

async fn done_todo(id: web::Path<u32,>) -> impl Responder {
    web::HttpResponse::Ok().json(SavedTodo {
        id: id.0,
        title: "dummy".to_string(),
        done: true,
    }).with_status(StatusCode::OK)
}

async fn delete_todo(id: web::Path<u32,>) -> impl Responder {
    web::HttpResponse::Ok().body("".to_string())
        .with_status(StatusCode::NO_CONTENT)
}

#[actix_web::main]
async fn main() -> io::Result<()> {
    let port = env::var("PORT")
        .unwrap_or_else(|_| "8080".to_string())
        .parse()
        .expect("PORT must be a number");

    HttpServer::new(move || {
        App::new()
            .route("/", web::get().to(status))
            .route("/todos{_:/?}", web::get().to(todos))
            .route("/todos{_:/?}", web::post().to(create_todo))
            .route("/todos/{id}", web::get().to(get_todo))
            .route("/todos/{id}", web::post().to(done_todo))
            .route("/todos/{id}", web::delete().to(delete_todo))
    })
        .bind(("0.0.0.0", port))?
        .run()
        .await
}
