Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
benlau6 committed Sep 2, 2024
0 parents commit 66e5903
Show file tree
Hide file tree
Showing 41 changed files with 1,503 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.gitignore
**/data
**/docs
.env?
**/migrations
**/scripts
10 changes: 10 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# for sqlx offline to run properly
POSTGRES_USER=myapp
POSTGRES_PASSWORD=password
DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/myapp

# for configuration.rs
APP_ENVIRONMENT=local
APP_DATABASE__USERNAME=${POSTGRES_USER}
APP_DATABASE__PASSWORD=${POSTGRES_PASSWORD}
APP_SECURITY__SECRET_KEY=secret
Empty file added .github/workflows/ci.yml
Empty file.
26 changes: 26 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# Custom
.env
data/
.sqlx/
65 changes: 65 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
[package]
name = "myapp"
version = "0.1.0"
edition = "2021"
default-run = "myapp"

[dependencies]
serde = { version = "1.0.204", features = ["derive"] }
serde_json = "1.0.122"
tokio = { version = "1.39.2", features = ["full"] }
axum = { version = "0.7.5", features = [
"macros",
"form",
"multipart",
"query",
] }
rayon = "1.10.0"
thiserror = "1.0.63"
tracing = { version = "0.1.40", features = ["attributes"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
jsonwebtoken = "9.3.0"
once_cell = "1.19.0"
bcrypt = "0.15.1"
chrono = { version = "0.4.38", features = ["clock", "serde"] }
tower-http = { version = "0.5.2", features = [
"cors",
"trace",
"timeout",
"limit",
] }
axum-extra = { version = "0.9.3", features = ["cookie", "typed-header"] }
sqlx = { version = "0.8.0", features = [
"runtime-tokio-rustls",
"postgres",
"macros",
"migrate",
"uuid",
"chrono",
"json",
] }
tower = "0.4.13"
uuid = { version = "1.10.0", features = ["serde", "v4"] }
async-trait = "0.1.81"
axum-macros = "0.4.1"
anyhow = "1.0.86"
dotenvy = "0.15.7"
# to generate random number
rand = "0.8.5"
itertools = "0.13.0"
cookie = "0.18.1"
config = "0.14.0"
# to opt-in password instead of opt-out
secrecy = { version = "0.8", features = ["serde"] }
# environment variables are strings for the config crate and it will fail to pick up integers
serde-aux = "4.5.0"
tracing-log = "0.2.0"
num = "0.4.3"
num-format = "0.4.4"
regex = "1.10.6"
rand_pcg = "0.3.1"
tracing-appender = "0.2.3"
log = "0.4.22"
env_logger = "0.11.5"
time = { version = "0.3.36", features = ["macros"] }
serde_with = "3.9.0"
61 changes: 61 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# syntax=docker/dockerfile:experimental
# The above step enabled specific experimental syntax, namely the
# "--mount-type=cache parameter, a named cache volume managed by
# Docker BuildKit
# https://juliankrieger.dev/blog/combining-docker-buildkit-cargo-chef

FROM lukemathwalker/cargo-chef:latest as chef

FROM chef as planner
WORKDIR /app
COPY . .
# Compute a lock-like file for our project
RUN cargo chef prepare --recipe-path recipe.json


FROM chef as cacher
WORKDIR /app
# Get the recipe file
COPY --from=planner /app/recipe.json recipe.json
# Build our project dependencies, not our application!
# this is the caching Docker layer!
# Cache dependencies
# I don't know why target=/app/target doesn't work
# but target=/target does
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/target \
cargo chef cook --release --recipe-path recipe.json


FROM chef as builder
WORKDIR /app
# Copy built dependencies over cache
COPY --from=cacher /app/target target
# Copy cargo folder from cache. This includes the package registry and downloaded sources
COPY --from=cacher $CARGO_HOME $CARGO_HOME
COPY . .
# needa run cargo sqlx prepare
ENV SQLX_OFFLINE true
# Build the binary
RUN --mount=type=cache,target=/usr/local/cargo/registry \
cargo build --release --bin myapp


# We do not need the Rust toolchain to run the binary!
FROM debian:bookworm-slim as runtime
WORKDIR /app
# Install OpenSSL - it is dynamically linked by some of our dependencies
# Install ca-certificates - it is needed to verify TLS certificates
# when establishing HTTPS connections
RUN apt-get update -y \
&& apt-get install -y --no-install-recommends openssl ca-certificates \
# Clean up
&& apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/target/release/myapp myapp
# Custom config
COPY configuration configuration
ENV APP_ENVIRONMENT development
ENTRYPOINT ["./myapp"]
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# fullstack-crud

## How to run the project

1. Set up the `.env` by following the `.env.example` file
2. Setting up the database
1. Spin up the database with `docker compose up`
2. Install `sqlx-cli` with `cargo install sqlx-cli`
3. Create the tables with `sqlx migrate run`
4. Enable sqlx building in "offline mode" with `cargo sqlx prepare`
3. Run the project with `cargo run`

## How to create tables

1. Create a migration file with `sqlx migrate add -r <migration_name>`
2. Write the up migration in the generated `.up.sql` file
3. Write the down migration in the generated `.down.sql` file
4. Run the migration with `sqlx migrate run`
5. (Optional) Rollback the migration with `sqlx migrate revert`
18 changes: 18 additions & 0 deletions configuration/base.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[application]
# We are using 127.0.0.1 as our host in address,
# we are instructing our application to only accept connec-ions coming from the same machine
host = "127.0.0.1"
port = 3000
rust_log = "rust_axum=debug,axum=debug,tower_http=debug,bullai=debug"

[database]
# for development using docker compose, we need to use the service name as host
# for api in docker-compose, host need to be service name, e.g. "postgres"
host = "localhost"
username = "it_should_be_override_by_.env"
password = "it_should_be_override_by_.env"
port = 5432
require_ssl = false

[security]
secret_key = "it_should_be_override_by_.env"
6 changes: 6 additions & 0 deletions configuration/development.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[application]
host = "0.0.0.0"
port = "80"

[database]
database_name = "myapp_dev"
6 changes: 6 additions & 0 deletions configuration/local.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[application]
# IPV6 localhost [::] is for macos
host = "[::]"

[database]
database_name = "myapp"
6 changes: 6 additions & 0 deletions configuration/production.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[application]
host = "0.0.0.0"
port = "80"

[database]
database_name = "myapp_prod"
14 changes: 14 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
services:
postgres:
image: postgres
restart: always
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data/
env_file: ".env"
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
postgres_data:
2 changes: 2 additions & 0 deletions migrations/20240902113425_create_user_table.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- Add down migration script here
DROP TABLE IF EXISTS users;
15 changes: 15 additions & 0 deletions migrations/20240902113425_create_user_table.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- Add up migration script here
CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE TABLE users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email varchar(255) UNIQUE NOT NULL,
hashed_password varchar(255) NOT NULL,
name varchar(255) NOT NULL,
is_active boolean NOT NULL DEFAULT true,
is_verified boolean NOT NULL DEFAULT false,
is_superuser boolean NOT NULL DEFAULT false,
created_at timestamp NOT NULL DEFAULT current_timestamp,
updated_at timestamp NOT NULL DEFAULT current_timestamp,
last_login timestamp
);
3 changes: 3 additions & 0 deletions scripts/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

docker compose build && docker image prune -f && docker compose up --remove-orphans
3 changes: 3 additions & 0 deletions sqlx-data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"db": "PostgreSQL"
}
5 changes: 5 additions & 0 deletions src/catalog.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod entity;
pub mod error;
pub mod handler;
pub mod pokemon;
pub mod service;
41 changes: 41 additions & 0 deletions src/catalog/entity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use num::{Bounded, Num};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct Range<T: num::Num> {
pub min: T,
pub q25: T,
pub q50: T,
pub q75: T,
pub max: T,
}

pub fn get_range<T: Ord + Num + Bounded + Copy>(values: &mut [T]) -> Range<T> {
let len = values.len();
if len == 0 {
tracing::error!("Creating range from zero len array, it is possibly a bug, returning a invalid range with all zeros");
return Range {
min: T::zero(),
q25: T::zero(),
q50: T::zero(),
q75: T::zero(),
max: T::zero(),
};
}
values.sort();
// we needa clone the quantiles
// because we need the values being stored as Range<T>
// while all of them should be remaining in the vector
let min = *values.iter().min().unwrap_or(&T::zero());
let max = *values.iter().max().unwrap_or(&T::max_value());
let q25 = *values.get(len / 4).unwrap_or(&T::zero());
let q50 = *values.get(len / 2).unwrap_or(&T::zero());
let q75 = *values.get(len * 3 / 4).unwrap_or(&T::zero());
Range {
min,
q25,
q50,
q75,
max,
}
}
30 changes: 30 additions & 0 deletions src/catalog/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde_json::json;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum CatalogError {
#[error("Resource not found")]
NotFound,
#[error("Resource not ready")]
NotImplemented,
#[error(transparent)]
UnexpectedError(#[from] anyhow::Error),
}

impl IntoResponse for CatalogError {
fn into_response(self) -> Response {
let status = match self {
CatalogError::NotFound | CatalogError::NotImplemented => StatusCode::NOT_FOUND,
_ => StatusCode::INTERNAL_SERVER_ERROR,
};
let body = Json(json!({
"message": self.to_string(),
}));
(status, body).into_response()
}
}
Loading

0 comments on commit 66e5903

Please sign in to comment.