Compare commits

...
Author SHA1 Message Date
Maulana Sodiqin d2c333dbd6 feat: split middleware 2025-04-07 20:41:14 +07:00
Maulana Sodiqin 054f0f63ff chore: adjust README.md 2025-04-07 20:08:26 +07:00
Maulana Sodiqin eb05ea5994 feat: add gacha service 2025-04-07 00:50:57 +07:00
44 changed files with 670 additions and 333 deletions
Generated
+52
View File
@@ -1871,6 +1871,30 @@ dependencies = [
"validator",
]
[[package]]
name = "imphnen-cms-service"
version = "0.1.0"
dependencies = [
"anyhow",
"axum",
"axum-test",
"chrono",
"imphnen-entities",
"imphnen-libs",
"imphnen-utils",
"lazy_static",
"rand 0.9.0",
"regex",
"serde",
"serde_json",
"surrealdb",
"tokio",
"tower-http",
"utoipa",
"utoipa-swagger-ui",
"validator",
]
[[package]]
name = "imphnen-dimentorin-service"
version = "0.1.0"
@@ -1941,6 +1965,7 @@ dependencies = [
"imphnen-entities",
"imphnen-iam-service",
"imphnen-libs",
"imphnen-middleware-service",
"imphnen-utils",
"lazy_static",
"rand 0.9.0",
@@ -1994,6 +2019,33 @@ dependencies = [
"tokio",
]
[[package]]
name = "imphnen-middleware-service"
version = "0.1.0"
dependencies = [
"anyhow",
"axum",
"axum-test",
"chrono",
"futures",
"imphnen-entities",
"imphnen-iam-service",
"imphnen-libs",
"imphnen-utils",
"lazy_static",
"rand 0.9.0",
"regex",
"serde",
"serde_json",
"surrealdb",
"tokio",
"tower",
"tower-http",
"utoipa",
"utoipa-swagger-ui",
"validator",
]
[[package]]
name = "imphnen-utils"
version = "0.1.0"
+75 -46
View File
@@ -1,78 +1,107 @@
# Axum SurrealDB Boilerplate
# IMPHNEN Backend Service
## Features
<p align="center">
<img src="docs/logo.svg" alt="IMPHNEN">
</p>
- **Authentication Ready**: Preconfigured authentication and middleware for secure API access.
- **Database Integration**: SurrealDB seamlessly integrated as an Axum Extension.
- **CORS Handling**: Fine-tuned CORS management with Tower HTTP `CorsLayer`.
- **API Documentation**: Fully documented with OpenAPI and Swagger UI.
- **Optimized for Performance**: Asynchronous, lightweight, and scalable architecture.
This repository serves as the **monorepo** for all backend services of IMPHNEN. It encompasses several main services:
## Prerequisites
1. **Core Service** - Provides fundamental functionalities and shared resources for other services.
2. **IAM Service** - Handles identity and access management across IMPHNEN applications.
3. **CMS Service** - Supports the cms services by IMPHNEN [Landing Page website](https://imphnen.dev/).
4. **Gacha Service** - Supports the gacha services by IMPHNEN [Gacha website](https://gacha.imphnen.dev/).
5. **Dimentorin Service** - Supports the mentoring services by IMPHNEN [Dimentorin website](https://dimentorin.imphnen.dev/).
6. **Gateway Service** - Acts as the API gateway, routing requests to appropriate services.
- **Rust**: Install Rust from [rust-lang.org](https://www.rust-lang.org/).
- **Database**: Set up a SurrealDB instance and configure connection details.
- **Docker**: Required for containerized deployment, install from [docker.com](https://www.docker.com/).
- **Nix (Optional)**: For reproducible builds, install from [nixos.org](https://nixos.org/).
## How to Install
## Getting Started
1. **Clone the repository**:
1. **Clone the Repository**:
```sh
git clone https://github.com/IMPHNEN/imphnen-backend-service.git
cd imphnen-backend-service
```
- `git clone https://github.com/maulanasdqn/axum-surrealdb-boilerplate`
2. **Set up the environment**:
2. **Set Up Environment Variables**:
- Copy the example environment files:
- Copy `.env.example` and rename it to `.env`
```sh
cp .env.example .env
```
- **Windows**: Run the script: `./apply-env.ps1`
- **Unix-based systems (Linux, macOS, BSD)**: Run the script: `./apply-env.sh`
if you use windows based system
3. **Install Dependencies**:
```sh
./apply-env.ps1
```
- `cargo install .`
if you use unix based system
4. **Setup Database**:
```sh
sh apply-env.sh
```
- Install the surrealDB
- **Windows**: `iwr https://windows.surrealdb.com -useb | iex`
- **Unix-based systems (Linux, macOS, BSD)**: `curl -sSf https://install.surrealdb.com | sh`
- Start the database `surreal start --user root --pass root`
- Modify the `.env` files with your specific configuration settings.
5. **Start the Server**:
3. **Install dependencies**:
- Install Cargo Watch `cargo install cargo-watch`
- Run it with cargo watch `cargo watch -x run`
Ensure you have [Rust](https://www.rust-lang.org/) installed. Then, run:
The API will be available at `http://localhost:3000/docs`.
```sh
cargo build
```
## Docker
## How to Run
1. **Build the Docker Image**:
### Development
2. **Run the Docker Container**:
To run the services in development mode:
The API will be accessible at `http://localhost:3000/docs`.
1. **Start the database and other dependencies** using Docker Compose:
## Using Nix as Builder (Optional)
```sh
docker-compose up -d
```
1. **Install Nix**:
2. **Run the desired service**. For example, to run the Core Service:
2. **Enter Nix Shell or Use Nix Flakes**:
```sh
cargo run -p imphnen-core-service --bin api
```
3. **Build the Project**:
### Production
4. **Run the Server**:
For production deployment:
## Contributing
1. **Build the Docker image**:
Contributions are welcome! Fork the repository and create a pull request with your improvements.
```sh
docker build -t imphnen-backend-service .
```
## License
2. **Run the Docker container**:
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
```sh
docker run -d --env-file .env -p 8080:8080 imphnen-backend-service
```
## Acknowledgements
Adjust the port and environment variables as needed.
- [Axum](https://github.com/tokio-rs/axum)
- [SurrealDB](https://github.com/surrealdb/surrealdb)
## How to Contribute
1. **Fork the repository** and clone it locally.
2. **Create a new branch** for your feature or fix:
```sh
git checkout -b feat/your-feature-name
```
3. **Make your changes**, commit them, and push to your forked repository.
4. **Create a pull request** to the `develop` branch of this repository.
If you encounter any issues or have questions, feel free to create a new issue in the repository.
---
_Note: For detailed API documentation, please refer to our [API Docs](https://api.imphnen.dev/docs)._
+8
View File
@@ -6,3 +6,11 @@ services:
ports:
- "${PORT}:${PORT}"
env_file: ".env"
depends_on:
- surrealdb
surrealdb:
image: surrealdb/surrealdb:latest
command: start --log trace --user root --pass root
ports:
- "8000:8000"
+9
View File
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 351 KiB

+24
View File
@@ -0,0 +1,24 @@
[package]
name = "imphnen-cms-service"
version = "0.1.0"
edition = "2024"
[dependencies]
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
axum.workspace = true
serde.workspace = true
serde_json.workspace = true
utoipa.workspace = true
lazy_static.workspace = true
regex.workspace = true
validator.workspace = true
axum-test.workspace = true
surrealdb.workspace = true
rand.workspace = true
tokio.workspace = true
chrono.workspace = true
anyhow.workspace = true
tower-http.workspace = true
utoipa-swagger-ui.workspace = true
+14
View File
@@ -0,0 +1,14 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
+2 -2
View File
@@ -4,9 +4,10 @@ version = "0.1.0"
edition = "2021"
[dependencies]
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
imphnen-gateway-service = { version = "0.1.0", path = "../imphnen-gateway-service" }
axum.workspace = true
serde.workspace = true
serde_json.workspace = true
@@ -22,4 +23,3 @@ chrono.workspace = true
anyhow.workspace = true
tower-http.workspace = true
utoipa-swagger-ui.workspace = true
imphnen-gateway-service = { version = "0.1.0", path = "../imphnen-gateway-service" }
@@ -1,93 +0,0 @@
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;
use utoipa::ToSchema;
use validator::Validate;
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
pub struct GachaItemRequestDto {
#[validate(length(min = 1, message = "Item name must not be empty"))]
pub name: String,
#[validate(length(min = 1, message = "Image URL must not be empty"))]
pub image_url: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
pub struct GachaClaimRequestDto {
#[validate(length(min = 1, message = "User ID must not be empty"))]
pub user_id: String,
#[validate(length(min = 1, message = "Item ID must not be empty"))]
pub item_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
pub struct GachaRollRequestDto {
#[validate(length(min = 1, message = "Item ID must not be empty"))]
pub item_id: String,
#[validate(range(min = 1, message = "Weight must be greater than zero"))]
pub weight: f32,
#[validate(range(min = 1, message = "Quantity must be at least 1"))]
pub quantity: i32,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct GachaItemDto {
pub id: String,
pub name: String,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaItemDtoRaw {
pub id: Thing,
pub name: String,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct GachaClaimDto {
pub id: String,
pub user: String,
pub item: String,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaClaimDtoRaw {
pub id: Thing,
pub user: Thing,
pub item: Thing,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct GachaRollDto {
pub id: String,
pub item: String,
pub weight: String,
pub quantity: i32,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaRollDtoRaw {
pub id: Thing,
pub item: Thing,
pub weight: String,
pub quantity: i32,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
@@ -1,92 +0,0 @@
use crate::{make_thing, ResourceEnum};
use serde::{Deserialize, Serialize};
use surrealdb::{sql::Thing, Uuid};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaItemSchema {
pub id: Thing,
pub name: String,
pub image_url: String,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
impl Default for GachaItemSchema {
fn default() -> Self {
GachaItemSchema {
id: make_thing(
&ResourceEnum::GachaItems.to_string(),
&Uuid::new_v4().to_string(),
),
name: String::new(),
image_url: String::new(),
is_deleted: false,
created_at: None,
updated_at: None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaClaimSchema {
pub id: Thing,
pub user: Thing,
pub item: Thing,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
impl Default for GachaClaimSchema {
fn default() -> Self {
GachaClaimSchema {
id: make_thing(
&ResourceEnum::GachaClaims.to_string(),
&Uuid::new_v4().to_string(),
),
user: make_thing(
&ResourceEnum::Users.to_string(),
&Uuid::new_v4().to_string(),
),
item: make_thing(
&ResourceEnum::GachaItems.to_string(),
&Uuid::new_v4().to_string(),
),
is_deleted: false,
created_at: None,
updated_at: None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaRollSchema {
pub id: Thing,
pub item: Thing,
pub weight: f32,
pub quantity: i32,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
impl Default for GachaRollSchema {
fn default() -> Self {
GachaRollSchema {
id: make_thing(
&ResourceEnum::GachaRolls.to_string(),
&Uuid::new_v4().to_string(),
),
item: make_thing(
&ResourceEnum::GachaItems.to_string(),
&Uuid::new_v4().to_string(),
),
weight: 0.2,
quantity: 2,
is_deleted: false,
created_at: None,
updated_at: None,
}
}
}
@@ -1,6 +0,0 @@
pub mod gacha_dto;
pub mod gacha_repository;
pub mod gacha_schema;
pub use gacha_repository::*;
pub use gacha_schema::*;
@@ -0,0 +1,33 @@
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;
use utoipa::ToSchema;
use validator::Validate;
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
pub struct GachaClaimRequestDto {
#[validate(length(min = 1, message = "User ID must not be empty"))]
pub user_id: String,
#[validate(length(min = 1, message = "Item ID must not be empty"))]
pub item_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct GachaClaimDto {
pub id: String,
pub user: String,
pub item: String,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaClaimDtoRaw {
pub id: Thing,
pub user: Thing,
pub item: Thing,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
@@ -0,0 +1,42 @@
use super::GachaClaimSchema;
use crate::{AppState, ResourceEnum};
use anyhow::{Result, bail};
pub struct GachaClaimRepository<'a> {
state: &'a AppState,
}
impl<'a> GachaClaimRepository<'a> {
pub fn new(state: &'a AppState) -> Self {
Self { state }
}
pub async fn query_gacha_claim_by_id(
&self,
id: String,
) -> Result<GachaClaimSchema> {
let db = &self.state.surrealdb_ws;
let result: Option<GachaClaimSchema> = db
.select((ResourceEnum::GachaClaims.to_string(), id.clone()))
.await?;
match result {
Some(claim) if !claim.is_deleted => Ok(claim),
_ => bail!("Gacha Claim not found"),
}
}
pub async fn query_create_gacha_claim(
&self,
data: GachaClaimSchema,
) -> Result<String> {
let db = &self.state.surrealdb_ws;
let record: Option<GachaClaimSchema> = db
.create(ResourceEnum::GachaClaims.to_string())
.content(data)
.await?;
match record {
Some(_) => Ok("Success create Gacha Claim".into()),
None => bail!("Failed to create Gacha Claim"),
}
}
}
@@ -0,0 +1,35 @@
use crate::{ResourceEnum, make_thing};
use serde::{Deserialize, Serialize};
use surrealdb::{Uuid, sql::Thing};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaClaimSchema {
pub id: Thing,
pub user: Thing,
pub item: Thing,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
impl Default for GachaClaimSchema {
fn default() -> Self {
GachaClaimSchema {
id: make_thing(
&ResourceEnum::GachaClaims.to_string(),
&Uuid::new_v4().to_string(),
),
user: make_thing(
&ResourceEnum::Users.to_string(),
&Uuid::new_v4().to_string(),
),
item: make_thing(
&ResourceEnum::GachaItems.to_string(),
&Uuid::new_v4().to_string(),
),
is_deleted: false,
created_at: None,
updated_at: None,
}
}
}
@@ -0,0 +1,7 @@
pub mod gacha_claim_dto;
pub mod gacha_claim_repository;
pub mod gacha_claim_schema;
pub use gacha_claim_dto::*;
pub use gacha_claim_repository::*;
pub use gacha_claim_schema::*;
@@ -0,0 +1,30 @@
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;
use utoipa::ToSchema;
use validator::Validate;
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
pub struct GachaItemRequestDto {
#[validate(length(min = 1, message = "Item name must not be empty"))]
pub name: String,
#[validate(length(min = 1, message = "Image URL must not be empty"))]
pub image_url: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct GachaItemDto {
pub id: String,
pub name: String,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaItemDtoRaw {
pub id: Thing,
pub name: String,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
@@ -1,15 +1,15 @@
use super::{GachaClaimSchema, GachaItemSchema, GachaRollSchema};
use super::GachaItemSchema;
use crate::{
AppState, MetaRequestDto, ResourceEnum, ResponseListSuccessDto, get_id,
make_thing, query_list_with_meta,
};
use anyhow::{Result, bail};
pub struct GachaRepository<'a> {
pub struct GachaItemRepository<'a> {
state: &'a AppState,
}
impl<'a> GachaRepository<'a> {
impl<'a> GachaItemRepository<'a> {
pub fn new(state: &'a AppState) -> Self {
Self { state }
}
@@ -97,59 +97,4 @@ impl<'a> GachaRepository<'a> {
None => bail!("Failed to delete Gacha Item"),
}
}
pub async fn query_gacha_claim_by_id(
&self,
id: String,
) -> Result<GachaClaimSchema> {
let db = &self.state.surrealdb_ws;
let result: Option<GachaClaimSchema> = db
.select((ResourceEnum::GachaClaims.to_string(), id.clone()))
.await?;
match result {
Some(claim) if !claim.is_deleted => Ok(claim),
_ => bail!("Gacha Claim not found"),
}
}
pub async fn query_create_gacha_claim(
&self,
data: GachaClaimSchema,
) -> Result<String> {
let db = &self.state.surrealdb_ws;
let record: Option<GachaClaimSchema> = db
.create(ResourceEnum::GachaClaims.to_string())
.content(data)
.await?;
match record {
Some(_) => Ok("Success create Gacha Claim".into()),
None => bail!("Failed to create Gacha Claim"),
}
}
pub async fn query_gacha_roll_by_id(&self, id: String) -> Result<GachaRollSchema> {
let db = &self.state.surrealdb_ws;
let result: Option<GachaRollSchema> = db
.select((ResourceEnum::GachaRolls.to_string(), id.clone()))
.await?;
match result {
Some(roll) if !roll.is_deleted => Ok(roll),
_ => bail!("Gacha Roll not found"),
}
}
pub async fn query_create_gacha_roll(
&self,
data: GachaRollSchema,
) -> Result<String> {
let db = &self.state.surrealdb_ws;
let record: Option<GachaRollSchema> = db
.create(ResourceEnum::GachaRolls.to_string())
.content(data)
.await?;
match record {
Some(_) => Ok("Success create Gacha Roll".into()),
None => bail!("Failed to create Gacha Roll"),
}
}
}
@@ -0,0 +1,29 @@
use crate::{ResourceEnum, make_thing};
use serde::{Deserialize, Serialize};
use surrealdb::{Uuid, sql::Thing};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaItemSchema {
pub id: Thing,
pub name: String,
pub image_url: String,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
impl Default for GachaItemSchema {
fn default() -> Self {
GachaItemSchema {
id: make_thing(
&ResourceEnum::GachaItems.to_string(),
&Uuid::new_v4().to_string(),
),
name: String::new(),
image_url: String::new(),
is_deleted: false,
created_at: None,
updated_at: None,
}
}
}
@@ -0,0 +1,7 @@
pub mod gacha_item_dto;
pub mod gacha_item_repository;
pub mod gacha_item_schema;
pub use gacha_item_dto::*;
pub use gacha_item_repository::*;
pub use gacha_item_schema::*;
@@ -0,0 +1,38 @@
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;
use utoipa::ToSchema;
use validator::Validate;
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
pub struct GachaRollRequestDto {
#[validate(length(min = 1, message = "Item ID must not be empty"))]
pub item_id: String,
#[validate(range(min = 1, message = "Weight must be greater than zero"))]
pub weight: f32,
#[validate(range(min = 1, message = "Quantity must be at least 1"))]
pub quantity: i32,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct GachaRollDto {
pub id: String,
pub item: String,
pub weight: String,
pub quantity: i32,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaRollDtoRaw {
pub id: Thing,
pub item: Thing,
pub weight: String,
pub quantity: i32,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
@@ -0,0 +1,39 @@
use super::GachaRollSchema;
use crate::{AppState, ResourceEnum};
use anyhow::{Result, bail};
pub struct GachaRollRepository<'a> {
state: &'a AppState,
}
impl<'a> GachaRollRepository<'a> {
pub fn new(state: &'a AppState) -> Self {
Self { state }
}
pub async fn query_gacha_roll_by_id(&self, id: String) -> Result<GachaRollSchema> {
let db = &self.state.surrealdb_ws;
let result: Option<GachaRollSchema> = db
.select((ResourceEnum::GachaRolls.to_string(), id.clone()))
.await?;
match result {
Some(roll) if !roll.is_deleted => Ok(roll),
_ => bail!("Gacha Roll not found"),
}
}
pub async fn query_create_gacha_roll(
&self,
data: GachaRollSchema,
) -> Result<String> {
let db = &self.state.surrealdb_ws;
let record: Option<GachaRollSchema> = db
.create(ResourceEnum::GachaRolls.to_string())
.content(data)
.await?;
match record {
Some(_) => Ok("Success create Gacha Roll".into()),
None => bail!("Failed to create Gacha Roll"),
}
}
}
@@ -0,0 +1,34 @@
use crate::{ResourceEnum, make_thing};
use serde::{Deserialize, Serialize};
use surrealdb::{Uuid, sql::Thing};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaRollSchema {
pub id: Thing,
pub item: Thing,
pub weight: f32,
pub quantity: i32,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
impl Default for GachaRollSchema {
fn default() -> Self {
GachaRollSchema {
id: make_thing(
&ResourceEnum::GachaRolls.to_string(),
&Uuid::new_v4().to_string(),
),
item: make_thing(
&ResourceEnum::GachaItems.to_string(),
&Uuid::new_v4().to_string(),
),
weight: 0.2,
quantity: 2,
is_deleted: false,
created_at: None,
updated_at: None,
}
}
}
@@ -0,0 +1,7 @@
pub mod gacha_roll_dto;
pub mod gacha_roll_repository;
pub mod gacha_roll_schema;
pub use gacha_roll_dto::*;
pub use gacha_roll_repository::*;
pub use gacha_roll_schema::*;
+7 -2
View File
@@ -1,2 +1,7 @@
pub mod gacha;
pub use gacha::*;
pub mod gacha_claim;
pub mod gacha_item;
pub mod gacha_roll;
pub use gacha_claim::*;
pub use gacha_item::*;
pub use gacha_roll::*;
+1
View File
@@ -5,6 +5,7 @@ edition = "2024"
[dependencies]
imphnen-iam-service = { version = "0.1.0", path = "../imphnen-iam-service" }
imphnen-middleware-service = { version = "0.1.0", path = "../imphnen-middleware-service" }
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
+1 -3
View File
@@ -5,10 +5,8 @@ use imphnen_entities::{AppState, SurrealMemClient, SurrealWsClient};
use imphnen_iam_service::{iam_protected_routes, iam_public_routes};
pub mod docs;
pub mod middleware;
pub use docs::*;
pub use middleware::*;
use imphnen_middleware_service::{auth_middleware, cors_middleware};
use utoipa_swagger_ui::SwaggerUi;
pub async fn gateway_service(
@@ -4,6 +4,7 @@ use std::fmt;
pub enum RolesEnum {
Admin,
User,
Student,
Staf,
}
@@ -12,6 +13,7 @@ impl fmt::Display for RolesEnum {
let roles_str = match self {
RolesEnum::Admin => "Admin",
RolesEnum::User => "User",
RolesEnum::Student => "Student",
RolesEnum::Staf => "Staf",
};
write!(f, "{}", roles_str)
View File
+27
View File
@@ -0,0 +1,27 @@
[package]
name = "imphnen-middleware-service"
version = "0.1.0"
edition = "2024"
[dependencies]
imphnen-iam-service = { version = "0.1.0", path = "../imphnen-iam-service" }
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
axum.workspace = true
serde.workspace = true
serde_json.workspace = true
utoipa.workspace = true
lazy_static.workspace = true
regex.workspace = true
validator.workspace = true
axum-test.workspace = true
surrealdb.workspace = true
rand.workspace = true
tokio.workspace = true
chrono.workspace = true
anyhow.workspace = true
tower-http.workspace = true
utoipa-swagger-ui.workspace = true
futures = "0.3.31"
tower = "0.5.2"
@@ -1,15 +1,11 @@
use axum::{
Extension,
extract::Request,
http::{HeaderValue, Method, StatusCode, header},
middleware::Next,
Extension, extract::Request, http::StatusCode, middleware::Next,
response::Response,
};
use imphnen_iam_service::{UsersItemDtoRaw, UsersRepository};
use imphnen_libs::{AppState, Env};
use imphnen_libs::AppState;
use imphnen_utils::{common_response, extract_email};
use std::convert::Infallible;
use tower_http::cors::CorsLayer;
pub async fn auth_middleware(
Extension(state): Extension<AppState>,
@@ -46,28 +42,3 @@ pub async fn auth_middleware(
req.extensions_mut().insert(user.unwrap());
Ok(next.run(req).await)
}
pub fn cors_middleware() -> CorsLayer {
let env = Env::new();
let cors_origins = match env.rust_env.as_str() {
"development" => vec!["http://localhost:3000"],
"production" => {
vec!["https://gacha.imphnen.dev", "https://imphnen.dev"]
}
_ => vec![
"http://localhost:3000",
"https://gacha.imphnen.dev",
"https://imphnen.dev",
],
};
let allowed_origins: Vec<HeaderValue> = cors_origins
.into_iter()
.filter_map(|origin| origin.parse::<HeaderValue>().ok())
.collect();
CorsLayer::new()
.allow_origin(allowed_origins)
.allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
.allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE])
.allow_credentials(true)
}
@@ -0,0 +1,33 @@
use axum::http::{HeaderValue, Method, header};
use imphnen_libs::Env;
use tower_http::cors::CorsLayer;
pub fn cors_middleware() -> CorsLayer {
let env = Env::new();
let cors_origins = match env.rust_env.as_str() {
"development" => vec!["http://localhost:3000"],
"production" => {
vec![
"https://gacha.imphnen.dev",
"https://imphnen.dev",
"https://dimentorin.imphnen.dev",
]
}
_ => vec![
"http://localhost:3000",
"https://gacha.imphnen.dev",
"https://imphnen.dev",
"https://dimentorin.imphnen.dev",
],
};
let allowed_origins: Vec<HeaderValue> = cors_origins
.into_iter()
.filter_map(|origin| origin.parse::<HeaderValue>().ok())
.collect();
CorsLayer::new()
.allow_origin(allowed_origins)
.allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
.allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE])
.allow_credentials(true)
}
+7
View File
@@ -0,0 +1,7 @@
pub mod auth_middleware;
pub mod cors_middleware;
pub mod permissions_middleware;
pub use auth_middleware::*;
pub use cors_middleware::*;
pub use permissions_middleware::*;
@@ -0,0 +1,102 @@
use axum::{
body::Body,
http::{Request, Response, StatusCode},
};
use futures::future::BoxFuture;
use imphnen_iam_service::{AuthRepository, PermissionsEnum};
use imphnen_libs::AppState;
use imphnen_utils::{common_response, extract_email};
use std::task::{Context, Poll};
use tower::{Layer, Service};
#[derive(Clone)]
pub struct PermissionsMiddlewareLayer {
app_state: AppState,
permissions: Vec<PermissionsEnum>,
}
impl PermissionsMiddlewareLayer {
pub fn new(app_state: AppState, permissions: Vec<PermissionsEnum>) -> Self {
Self {
app_state,
permissions,
}
}
}
impl<S> Layer<S> for PermissionsMiddlewareLayer {
type Service = PermissionsMiddleware<S>;
fn layer(&self, inner: S) -> Self::Service {
PermissionsMiddleware {
inner,
app_state: self.app_state.clone(),
permissions: self.permissions.clone(),
}
}
}
#[derive(Clone)]
pub struct PermissionsMiddleware<S> {
inner: S,
app_state: AppState,
permissions: Vec<PermissionsEnum>,
}
impl<S> Service<Request<Body>> for PermissionsMiddleware<S>
where
S: Service<Request<Body>, Response = Response<Body>> + Clone + Send + 'static,
S::Future: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
let mut inner = self.inner.clone();
let app_state = self.app_state.clone();
let permissions = self.permissions.clone();
Box::pin(async move {
let headers = req.headers();
let email = match extract_email(headers) {
Some(email) => email,
None => {
return Ok(common_response(
StatusCode::UNAUTHORIZED,
"Invalid or missing authorization token",
));
}
};
let auth_repo = AuthRepository::new(&app_state);
let user = match auth_repo.query_get_stored_user(email).await {
Ok(user) => user,
Err(_) => {
return Ok(common_response(
StatusCode::UNAUTHORIZED,
"User session expired or not found",
));
}
};
let user_permissions: Vec<String> =
user.role.permissions.into_iter().map(|p| p.name).collect();
let allowed = permissions
.iter()
.all(|p| user_permissions.contains(&p.to_string()));
if !allowed {
return Ok(common_response(
StatusCode::FORBIDDEN,
"You don't have the required permissions",
));
}
inner.call(req).await
})
}
}