Compare commits

...
Author SHA1 Message Date
MythEclipse 02be09506a refactor: update surrealdb client usage to support 'any' engine type 2025-06-29 23:23:18 +07:00
Maulana Sodiqin b88ecd1361 Merge pull request #37 from bakunya/develop
crud testi: acc banh
2025-06-29 00:34:44 +07:00
0x6d696b7566616e fa435994f2 crud testi 2025-06-29 00:01:00 +07:00
Maulana Sodiqin 16c613d699 Merge pull request #36 from IMPHNEN/feat/dotenv-dynamic-engine-seeder
feat: add dotenvy for environment variable management
2025-06-28 20:50:20 +07:00
MythEclipse f598dedb3b feat: add dotenvy for environment variable management and improve error handling in Env struct 2025-06-28 20:21:20 +07:00
Maulana Sodiqin cfa251337f Create deploy-to-ancikri.yml 2025-05-30 15:40:41 +07:00
Maulana Sodiqin cca9aa6318 chore: optimize dockerfile 2025-05-26 23:41:00 +07:00
Maulana Sodiqin f1e4e02697 fix: landing 2025-05-26 23:09:01 +07:00
Maulana Sodiqin a3c2715a85 Merge pull request #25 from bakunya/develop
feat: cms event
2025-05-26 15:28:05 +07:00
0x6d696b7566616e 89cc631fb0 feat: cms event 2025-05-26 14:11:17 +07:00
Maulana Sodiqin 36c12110c2 fixing dockerfile 2025-05-22 22:29:28 +07:00
Maulana Sodiqin 070060365a docs: update readme 2025-05-22 16:18:15 +07:00
Maulana Sodiqin 68e816f956 fix: schema default value 2025-05-22 15:25:24 +07:00
Maulana Sodiqin 48db5f07ef fix: register roles missmatch 2025-05-22 15:13:58 +07:00
Maulana Sodiqin a4b5d91906 chore: setup folder structure 2025-05-22 00:40:40 +07:00
Maulana Sodiqin 9878279660 feat: add gacha credits 2025-05-22 00:31:36 +07:00
Maulana Sodiqin 465cbee2c3 feat: finishing gacha feature 2025-05-21 19:37:30 +07:00
Maulana Sodiqin 33a2ae86c6 fix: unit tests 2025-05-21 11:35:35 +07:00
Maulana Sodiqin d906fc4747 chore: update query list 2025-05-20 16:57:45 +07:00
Maulana Sodiqin 055ef6ac8e feat: adjust readme and add new seeder script 2025-05-20 11:54:15 +07:00
Maulana Sodiqin e63658c082 feat: add gacha and init cms 2025-05-20 10:45:01 +07:00
Maulana Sodiqin f4b0ed0a4a Merge pull request #24 from IMPHNEN/chore/normalize-code
chore: normalize code
2025-05-20 01:50:58 +07:00
Maulana Sodiqin 1e5ed26f76 chore: cleaning code 2025-05-20 01:49:50 +07:00
Maulana Sodiqin d97095a38d chore: cleaning code 2025-05-20 00:26:24 +07:00
Maulana Sodiqin e48507901e chore: normalize code #2 2025-05-18 03:12:20 +07:00
Maulana Sodiqin a43ef8ec04 chore: normalize code 2025-05-17 02:08:59 +07:00
Maulana Sodiqin 874f25aa80 Merge pull request #23 from bakunya/develop
transform id(thing) to id(string) on get role, permission, users
2025-05-04 17:24:13 +07:00
Bakunya 12a730a394 transform id(thing) to id(string) on get role, permission, users 2025-05-04 17:07:20 +07:00
Bakunya bdcd41f54d transform id(thing) to id(string) on get role, permission, users 2025-05-04 16:37:11 +07:00
Maulana Sodiqin 96fb8eb7d3 Merge pull request #22 from IMPHNEN/feat/split-middleware
feat: split middleware
2025-04-07 20:43:59 +07:00
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
Maulana Sodiqin 4f870ec4c9 fix: unit test 2025-04-06 23:35:02 +07:00
Maulana Sodiqin 31f9b6cfea feat: microservice ready 2025-04-06 22:13:46 +07:00
Maulana Sodiqin 07a93ff0b7 Merge pull request #21 from bakunya/develop
fix otp and password validation
2025-04-06 20:09:17 +07:00
Bakunya d2b098ed92 fix otp and password validation 2025-04-06 19:15:01 +07:00
Maulana Sodiqin 09a7f4371a chore: remove uneeded cargo package 2025-04-06 13:28:15 +07:00
Maulana Sodiqin b4bf78f0d6 chore: re-map project structure to using multi workspace paradigm 2025-04-06 13:25:44 +07:00
Maulana Sodiqin de9c5a6dc1 feat: implement guard 2025-04-02 09:35:37 +07:00
Maulana Sodiqin 8a661df059 feat: add image url to gacha DTO 2025-04-01 20:05:07 +07:00
Maulana Sodiqin c7c16d4197 feat: add image url 2025-04-01 20:04:28 +07:00
Maulana Sodiqin 3aec0346a9 fix: wrong id 2025-04-01 20:01:40 +07:00
Maulana Sodiqin 8d2f720de1 feat: add gacha 2025-04-01 20:00:00 +07:00
Maulana Sodiqin 4b8da68df4 feat: gacha 2025-03-30 18:58:17 +07:00
Maulana Sodiqin 6460979bab feat: complete backend service 2025-03-30 16:08:58 +07:00
Maulana Sodiqin 3ae9dbd717 feat: success test 2025-03-30 14:13:45 +07:00
Maulana Sodiqin 0816566d03 feat: update logic 2025-03-30 02:46:16 +07:00
Maulana Sodiqin a97044edfc fix: seeder roles permissions 2025-03-28 17:18:07 +07:00
Maulana Sodiqin 9d92a9a136 feat: pagination 2025-03-26 13:06:34 +07:00
Maulana Sodiqin 8e12c79ea6 feat: permissions controller 2025-03-25 20:50:42 +07:00
Maulana Sodiqin 5777268441 feat: fixing role and auth test 2025-03-25 19:30:02 +07:00
Maulana Sodiqin 34eaa418d5 feat: roles router 2025-03-25 10:48:34 +07:00
Maulana Sodiqin 77f12610dd fix: test user 2025-03-24 13:12:54 +07:00
Maulana Sodiqin 5be737ad70 chore: add test coverage 2025-03-23 20:42:38 +07:00
Maulana Sodiqin 0ae1e26ba8 chore: complete unit test of user and authentication system 2025-03-23 12:32:00 +07:00
Maulana Sodiqin 79721b7af6 chore: implement unit test 2025-03-23 07:56:43 +07:00
Maulana Sodiqin 093e290221 feat: complete auth system 2025-03-23 01:19:17 +07:00
Maulana Sodiqin 1e6957720e feat: user management 2025-03-21 17:00:19 +07:00
Maulana Sodiqin a0f263bb4f feat: update query 2025-03-21 13:25:22 +07:00
Maulana Sodiqin 413a28c089 feat: setup data 2025-03-21 05:36:00 +07:00
Maulana Sodiqin a90e8662e1 feat: initial commit 2025-03-20 22:41:20 +07:00
Maulana Sodiqin 955d384f96 fix: docker 2025-03-20 17:36:43 +07:00
Maulana Sodiqin ea5ece9ab2 feat: initial commit 2025-03-20 17:33:57 +07:00
Maulana Sodiqin 8ff532bb67 fix: cors 2025-03-20 17:07:38 +07:00
Maulana Sodiqin eb6cfd51ae feat: auth functionality 2025-03-19 23:17:50 +07:00
Maulana Sodiqin 11d6249007 feat: docs 2025-03-19 20:55:29 +07:00
Maulana Sodiqin 497c250e6d fix: change deployed branch 2025-03-19 19:34:57 +07:00
Maulana Sodiqin 2872c1827a feat: deploy 2025-03-19 19:33:56 +07:00
Maulana Sodiqin f9c49d44b5 feat: gacha roll 2025-03-19 05:34:39 +07:00
Maulana Sodiqin 12043f027c feat: gacha roll, user auth 2025-03-18 14:13:53 +07:00
Maulana Sodiqin 0c28d34d57 feat: apply env 2025-03-17 09:27:19 +07:00
Maulana Sodiqin 271b00c2c4 feat: create gacha item 2025-03-15 02:23:27 +07:00
Maulana Sodiqin b096f4129a feat: add middleware 2025-03-15 01:53:20 +07:00
Maulana Sodiqin 7ab036f5f7 feat: user relation 2025-03-14 17:50:37 +07:00
Maulana Sodiqin 9ffe808797 feat: basic auth 2025-03-14 09:53:20 +07:00
Maulana Sodiqin acfb8259bc feat: auth 2025-03-13 23:40:16 +07:00
Maulana Sodiqin 298144bd36 chore: fix nix 2025-02-26 15:55:48 +07:00
Maulana Sodiqin ff9d8cba69 feat: add cargo 2025-02-26 15:45:39 +07:00
Maulana Sodiqin 8b2769706e feat: add cargo 2025-02-26 15:45:05 +07:00
Maulana Sodiqin b8606ef0bb chore: remove seaorm 2025-02-25 02:02:01 +07:00
Maulana Sodiqin 44de53a49d feat: implement surrealdb and redis 2025-02-25 02:00:19 +07:00
Maulana Sodiqin 9abab01b9c chore: re-format file 2025-02-16 01:09:55 +07:00
Maulana Sodiqin 53414687ff chore: initial commit 2025-02-16 01:02:51 +07:00
Maulana Sodiqin d2ec21c7b1 chore: add readme 2025-02-08 01:16:14 +07:00
Maulana Sodiqin 4deee3a9cb chore: configure action 2025-02-08 01:10:48 +07:00
Maulana Sodiqin b50ce2b060 Merge pull request #1 from IMPHNEN/feat/auth-login
feat: auth login
2025-02-08 01:09:57 +07:00
181 changed files with 12097 additions and 1236 deletions

No files matched your search

+5 -1
View File
@@ -1,4 +1,8 @@
PORT=
DATABASE_URL=
SURREALDB_URL=
SURREALDB_USERNAME=
SURREALDB_PASSWORD=
SURREALDB_NAMESPACE=
SURREALDB_DBNAME=
ACCESS_TOKEN_SECRET=
REFRESH_TOKEN_SECRET=
+65
View File
@@ -0,0 +1,65 @@
name: Deploy to Ancikri
on:
push:
branches:
- develop
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Build the project
run: cargo build --release
- name: Stop service on VPS before upload
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.VPS_ANCIKRI_IP }}
username: ${{ secrets.VPS_ANCIKRI_USER }}
key: ${{ secrets.VPS_ANCIKRI_SSH_KEY }}
port: ${{ secrets.VPS_ANCIKRI_PORT }}
script: |
set -e
echo "Stopping the service before uploading the binary"
sudo systemctl stop imphnen-backend-service
- name: Upload artifact to VPS
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.VPS_ANCIKRI_IP }}
username: ${{ secrets.VPS_ANCIKRI_USER }}
key: ${{ secrets.VPS_ANCIKRI_SSH_KEY }}
port: ${{ secrets.VPS_ANCIKRI_PORT }}
source: ./target/release/*
target: /opt/imphnen-backend-service/imphnen-backend-service
rm: true
overwrite: true
- name: Deploy to server
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.VPS_ANCIKRI_IP }}
username: ${{ secrets.VPS_ANCIKRI_USER }}
key: ${{ secrets.VPS_ANCIKRI_SSH_KEY }}
port: ${{ secrets.VPS_ANCIKRI_PORT }}
script: |
set -e
echo "Restarting the service"
sudo systemctl daemon-reload
sudo systemctl restart imphnen-backend-service
echo "Deployment completed successfully"
+65
View File
@@ -0,0 +1,65 @@
name: Deploy
on:
push:
branches:
- develop
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Build the project
run: cargo build --release
- name: Stop service on VPS before upload
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.VPS_IP }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
port: ${{ secrets.VPS_PORT }}
script: |
set -e
echo "Stopping the service before uploading the binary"
sudo systemctl stop imphnen-backend-service
- name: Upload artifact to VPS
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.VPS_IP }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
port: ${{ secrets.VPS_PORT }}
source: ./target/release/*
target: /opt/imphnen-backend-service/imphnen-backend-service
rm: true
overwrite: true
- name: Deploy to server
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.VPS_IP }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
port: ${{ secrets.VPS_PORT }}
script: |
set -e
echo "Restarting the service"
sudo systemctl daemon-reload
sudo systemctl restart imphnen-backend-service
echo "Deployment completed successfully"
+19
View File
@@ -0,0 +1,19 @@
name: Rust
on:
push:
branches: ["develop"]
pull_request:
branches: ["develop"]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: cargo build --verbose
+1
View File
@@ -4,6 +4,7 @@
# Nix
/.direnv
/Cargo.nix
# Environment
.envrc
Generated
+3237 -869
View File
File diff suppressed because it is too large. Load diff
+36 -11
View File
@@ -1,22 +1,47 @@
[package]
name = "imphnen-cms-be"
version = "0.1.0"
edition = "2021"
[workspace]
resolver = "2"
members = [
"tests",
"imphnen-iam",
"imphnen-cms",
"imphnen-libs",
"imphnen-utils",
"imphnen-gacha",
"imphnen-gateway",
"imphnen-backend",
"imphnen-entities",
"imphnen-dimentorin",
"imphnen-middleware",
]
[dependencies]
axum = { version = "0.8.1", features = ["multipart"] }
[workspace.dependencies]
axum = { version = "0.8.4", features = ["multipart"] }
log = "0.4.25"
sea-orm = { version = "1.1.4", features = ["sqlx-postgres", "macros", "with-json", "with-uuid", "runtime-tokio", "runtime-tokio-native-tls"] }
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.138"
tokio = { version = "1.43.0", features = ["full"] }
tokio = { version = "1.45.0" }
argon2 = { version = "0.5.3", features = ["password-hash"] }
jsonwebtoken = "9.3.1"
chrono = "0.4.39"
chrono = "0.4.41"
utoipa = { version = "5.3.1", features = ["axum_extras"] }
utoipa-swagger-ui = { version = "9.0.0", features = ["axum"] }
redis = "0.28.2"
lettre = { version = "0.11.16", features = ["tokio1-native-tls"] }
surrealdb = { version = "2.3.2", features = ["kv-mem"] }
thiserror = "2.0.12"
anyhow = "1.0.98"
rand = { version = "0.9.1", features = ["std", "alloc"] }
rand_distr = "0.5.1"
tower-http = { version = "0.6.4", features = ["cors"] }
validator = { version = "0.12", features = ["derive"] }
lazy_static = "1.4.0"
regex = "1.11.1"
axum-test = "17.2.0"
fancy-regex = "0.14.0"
futures = "0.3.31"
tower = "0.5.2"
env_logger = "0.11.8"
dotenvy = "0.15.7"
[profile.release]
lto = "fat"
codegen-units = 1
+37 -12
View File
@@ -1,22 +1,47 @@
FROM rust:1.83-slim-bullseye AS builder
FROM rust:1.85-alpine AS builder
RUN apt-get update && apt-get install -y \
build-essential \
libssl-dev \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
RUN apk add --no-cache \
curl \
musl-dev \
openssl-dev \
openssl-libs-static \
pkgconfig
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
COPY ./src ./src
RUN cargo build --release && strip /app/target/release/imphnen-cms-api
RUN mkdir -p imphnen-backend/src imphnen-cms/src imphnen-dimentorin/src \
imphnen-entities/src imphnen-gacha/src imphnen-gateway/src \
imphnen-iam/src imphnen-libs/src imphnen-middleware/src \
imphnen-utils/src tests/src && \
echo "fn main() {}" > imphnen-backend/src/main.rs && \
find . -name "src" -type d -exec sh -c 'echo "// dummy" > "$1/lib.rs"' _ {} \;
FROM gcr.io/distroless/cc AS runner
RUN echo '[package]\nname = "tests"\nversion = "0.1.0"\nedition = "2021"' > tests/Cargo.toml
WORKDIR /app
COPY --from=builder /app/target/release/imphnen-cms-api .
RUN echo -e '[package]\nname = "tests"\nversion = "0.1.0"\nedition = "2021"' > tests/Cargo.toml
CMD ["/app/imphnen-cms-api"]
COPY imphnen-backend ./imphnen-backend
COPY imphnen-cms ./imphnen-cms
COPY imphnen-dimentorin ./imphnen-dimentorin
COPY imphnen-entities ./imphnen-entities
COPY imphnen-gacha ./imphnen-gacha
COPY imphnen-gateway ./imphnen-gateway
COPY imphnen-iam ./imphnen-iam
COPY imphnen-libs ./imphnen-libs
COPY imphnen-middleware ./imphnen-middleware
COPY imphnen-utils ./imphnen-utils
COPY tests ./tests
RUN RUSTFLAGS="-C target-cpu=generic -C opt-level=s -C panic=abort -C codegen-units=1 -C strip=symbols" \
cargo build -p imphnen-backend --release && \
strip target/release/api && \
upx --best --lzma target/release/api 2>/dev/null || true
FROM scratch AS runner
COPY --from=builder /app/target/release/api /api
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/api"]
+130
View File
@@ -0,0 +1,130 @@
# IMPHNEN Backend Service
<p align="center">
<img src="docs/logo.svg" alt="IMPHNEN">
</p>
This repository serves as the **monorepo** for all backend services of IMPHNEN. It encompasses several main services:
1. **IMPHNEN-Backend** - Provides fundamental functionalities and shared resources for other services.
2. **IMPHNEN-IAM** - Handles identity and access management across IMPHNEN applications.
3. **IMPHNEN-CMS** - Supports the cms services by IMPHNEN [Landing Page website](https://imphnen.dev/).
4. **IMPHNEN-Gacha** - Supports the gacha services by IMPHNEN [Gacha website](https://gacha.imphnen.dev/).
5. **IMPHNEN-Dimentorin** - Supports the mentoring services by IMPHNEN [Dimentorin website](https://dimentorin.imphnen.dev/).
6. **IMPHNEN-Gateway** - Acts as the API gateway, routing requests to appropriate services.
7. **IMPHNEN-Middleware** - Acts as the middleware for the API Gateway, providing authentication and authorization.
## How to Install
1. **Clone the repository**:
```sh
git clone https://github.com/IMPHNEN/imphnen-backend-service.git
cd imphnen-backend-service
```
2. **Set up the environment**:
- Copy the example environment files:
```sh
cp .env.example .env
```
if you use windows based system
```sh
./apply-env.ps1
```
if you use unix based system
```sh
source ./apply-env.sh
```
- Modify the `.env` files with your specific configuration settings.
3. **Install dependencies**:
Ensure you have [Rust](https://www.rust-lang.org/) installed. Then, run:
```sh
cargo fetch
```
4. **Run the seeders**:
to run the seeders, run:
```sh
cargo run --bin seeder
```
## How to Run
### Development
To run the services in development mode:
1. **Start the database and other dependencies** using Docker Compose:
```sh
docker-compose up -d
```
2. **Run using cargo run**. For example, to run the Core Service:
```sh
cargo run --bin api
```
3. **Run using cargo watch**. For example, to run the Core Service:
```sh
cargo watch -x "run --bin api"
```
### Production
For production deployment:
1. **Build the Docker image**:
```sh
docker build -t imphnen-backend .
```
2. **Run the Docker container**:
```sh
docker run --name imphnen-backend -d --env-file .env -p 3000:3000 imphnen-backend
```
Adjust the port and environment variables as needed.
## How to Run the Tests
1. **Run the tests**:
```sh
cargo test -p tests
```
## 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)._
+35
View File
@@ -0,0 +1,35 @@
@echo off
setlocal
:: Cek apakah file .env ada
if not exist ".env" (
echo File .env tidak ditemukan di direktori saat ini.
exit /b 1
)
echo Memuat variabel dari .env...
:: Baca file .env baris per baris
for /f "tokens=*" %%a in ('type ".env" ^| findstr /v "^$" ^| findstr /v "^#"') do (
echo.%%a | findstr "=" >nul && (
for /f "tokens=1,2 delims==" %%b in ("%%a") do (
set "key=%%b"
set "value=%%c"
:: Trim whitespace
call :trimValue key value
echo Set variabel: %%b=%%c
setx %%b %%c >nul
)
)
)
echo.
echo Semua variabel telah dimuat.
endlocal
goto :eof
:: Fungsi trim (sederhana)
:trimValue
set "%1=%[%1]%"
set "%2=%[%2]%"
goto :eof
+30
View File
@@ -0,0 +1,30 @@
function Set-TempEnvFromDotEnv {
param (
[string]$envFilePath
)
if (-Not (Test-Path $envFilePath)) {
Write-Error "The .env file at path '$envFilePath' does not exist."
return
}
$envContent = Get-Content $envFilePath
foreach ($line in $envContent) {
$trimmedLine = $line.Trim()
if (-Not [string]::IsNullOrWhiteSpace($trimmedLine) -and -Not $trimmedLine.StartsWith("#")) {
$keyValue = $trimmedLine -split "=", 2
if ($keyValue.Length -eq 2) {
$key = $keyValue[0].Trim()
$value = $keyValue[1].Trim()
[System.Environment]::SetEnvironmentVariable($key, $value, [System.EnvironmentVariableTarget]::Process)
Write-Host "Set temporary environment variable: $key=$value"
}
}
}
Write-Host "All environment variables from '$envFilePath' have been set temporarily."
}
Set-TempEnvFromDotEnv -envFilePath ".env"
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
set_temp_env_from_dotenv() {
local env_file_path="$1"
if [[ ! -f "$env_file_path" ]]; then
echo "Error: The .env file at path '$env_file_path' does not exist."
return 1
fi
while IFS= read -r line || [[ -n "$line" ]]; do
trimmed_line=$(echo "$line" | xargs)
if [[ -n "$trimmed_line" && ! "$trimmed_line" =~ ^# ]]; then
key=$(echo "$trimmed_line" | cut -d '=' -f 1 | xargs)
value=$(echo "$trimmed_line" | cut -d '=' -f 2- | xargs)
export "$key=$value"
echo "Set temporary environment variable: $key=$value"
fi
done < "$env_file_path"
echo "All environment variables from '$env_file_path' have been set temporarily."
}
set_temp_env_from_dotenv ".env"
+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
@@ -0,0 +1,9 @@
<svg width="317" height="170" viewBox="0 0 317 170" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="317" height="169.613" fill="url(#pattern0_164_317)"/>
<defs>
<pattern id="pattern0_164_317" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_164_317" transform="scale(0.00184502 0.00344828)"/>
</pattern>
<image id="image0_164_317" width="542" height="290" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAh4AAAEiCAYAAAC2k9sBAAAABGdBTUEAALGPC/xhBQAACklpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAAEiJnVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKLine truncated
</defs>
</svg>

After

Width:  |  Height:  |  Size: 351 KiB

Generated
+27
View File
@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1739020877,
"narHash": "sha256-mIvECo/NNdJJ/bXjNqIh8yeoSjVLAuDuTUzAo7dzs8Y=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a79cfe0ebd24952b580b1cf08cd906354996d547",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+13 -5
View File
@@ -1,24 +1,32 @@
{
description = "IMPHNEN CMS API Nix Flake";
description = "IMPHNEN Backend Service Nix Flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
};
outputs = {
self,
nixpkgs,
}: let
supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-darwin" "aarch64-linux"];
pkgsFor = system:
import nixpkgs {
inherit system;
config = {
allowUnfree = true;
};
};
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
pkgsFor = nixpkgs.legacyPackages;
in {
packages = forAllSystems (system: {
default = pkgsFor.${system}.callPackage ./default.nix {};
default = (pkgsFor system).callPackage ./default.nix {};
});
devShells = forAllSystems (system: {
default = pkgsFor.${system}.callPackage ./shell.nix {};
default = (pkgsFor system).callPackage ./shell.nix {};
});
dockerImages = forAllSystems (system: {
tryOutApi = pkgsFor.${system}.callPackage ./docker.nix {};
tryOutApi = (pkgsFor system).callPackage ./docker.nix {};
});
};
}
+28
View File
@@ -0,0 +1,28 @@
[package]
name = "imphnen-backend"
version = "0.1.0"
edition = "2021"
[dependencies]
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
imphnen-gateway = { version = "0.1.0", path = "../imphnen-gateway" }
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
imphnen-iam = { version = "0.1.0", path = "../imphnen-iam" }
imphnen-cms = { version = "0.1.0", path = "../imphnen-cms" }
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
env_logger.workspace = true
+12
View File
@@ -0,0 +1,12 @@
use env_logger;
use imphnen_gateway::gateway_service;
use imphnen_libs::axum_init;
#[tokio::main]
async fn main() {
env_logger::init();
axum_init(|surrealdb_ws, surrealdb_mem| async {
gateway_service(surrealdb_ws, surrealdb_mem).await
})
.await;
}
+93
View File
@@ -0,0 +1,93 @@
use imphnen_cms::v1::landing::events::events_schema::EventsSchema;
use imphnen_utils::{get_iso_date, Env};
use std::error::Error;
use surrealdb::engine::any;
use surrealdb::{opt::auth::Root, sql::Thing};
use imphnen_libs::enviroment::load_env;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
load_env();
let env = Env::new();
let db = any::connect(&env.surrealdb_url).await?;
db.signin(Root {
username: &env.surrealdb_username,
password: &env.surrealdb_password,
})
.await?;
db.use_ns(env.surrealdb_namespace)
.use_db(env.surrealdb_dbname)
.await?;
let events = vec![
(
"e1a2b3c4-5d6e-7f8g-9h0i-1j2k3l4m5n6o",
"Tech Conference 2025",
"Annual technology conference featuring the latest innovations in software development, AI, and cloud computing.",
"https://techconf2025.example.com",
150.0,
Some("Jakarta Convention Center".to_string()),
false,
"2025-06-15T09:00:00Z",
"2025-06-17T18:00:00Z",
),
(
"f2b3c4d5-6e7f-8g9h-0i1j-2k3l4m5n6o7p",
"Online Web Development Workshop",
"Comprehensive workshop covering modern web development frameworks including React, Vue, and Angular.",
"https://webdev-workshop.example.com",
75.0,
None,
true,
"2025-07-10T14:00:00Z",
"2025-07-10T17:00:00Z",
),
(
"g3c4d5e6-7f8g-9h0i-1j2k-3l4m5n6o7p8q",
"Startup Pitch Competition",
"Exciting competition where emerging startups present their innovative ideas to a panel of expert judges and investors.",
"https://startup-pitch.example.com",
25.0,
Some("Innovation Hub Surabaya".to_string()),
false,
"2025-08-05T10:00:00Z",
"2025-08-05T16:00:00Z",
),
(
"h4d5e6f7-8g9h-0i1j-2k3l-4m5n6o7p8q9r",
"Digital Marketing Masterclass",
"Learn advanced digital marketing strategies, social media optimization, and data-driven marketing techniques.",
"https://digital-marketing.example.com",
100.0,
None,
true,
"2025-09-20T13:00:00Z",
"2025-09-22T15:00:00Z",
),
];
for (id, name, description, detail_link, price, location, is_online, start_date, end_date) in events {
let event = EventsSchema {
id: Thing::from(("app_events", id)),
name: name.into(),
description: description.into(),
detail_link: detail_link.into(),
price,
location,
is_online,
is_deleted: false,
start_date: start_date.into(),
end_date: end_date.into(),
created_at: get_iso_date(),
updated_at: get_iso_date(),
};
db.create::<Option<EventsSchema>>(("app_events", id))
.content(event)
.await?;
println!("✅ Inserted event: {} ({})", name, if is_online { "Online" } else { "In-person" });
}
println!("✅ All Events seeded");
Ok(())
}
@@ -0,0 +1,69 @@
use imphnen_iam::PermissionsEnum;
use imphnen_libs::enviroment::load_env;
use imphnen_utils::{get_iso_date, Env};
use serde_json::json;
use std::error::Error;
use surrealdb::engine::any;
use surrealdb::opt::auth::Root;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
load_env();
let env = Env::new();
let db = any::connect(&env.surrealdb_url).await?;
db.signin(Root {
username: &env.surrealdb_username,
password: &env.surrealdb_password,
})
.await?;
db.use_ns(env.surrealdb_namespace)
.use_db(env.surrealdb_dbname)
.await?;
for permission in [
PermissionsEnum::ReadListUsers,
PermissionsEnum::ReadDetailUsers,
PermissionsEnum::CreateUsers,
PermissionsEnum::DeleteUsers,
PermissionsEnum::UpdateUsers,
PermissionsEnum::ActivateUsers,
PermissionsEnum::ReadListRoles,
PermissionsEnum::ReadDetailRoles,
PermissionsEnum::CreateRoles,
PermissionsEnum::DeleteRoles,
PermissionsEnum::UpdateRoles,
PermissionsEnum::ReadListPermissions,
PermissionsEnum::ReadDetailPermissions,
PermissionsEnum::CreatePermissions,
PermissionsEnum::DeletePermissions,
PermissionsEnum::UpdatePermissions,
PermissionsEnum::CreateGachaClaims,
PermissionsEnum::ReadDetailGachaClaims,
PermissionsEnum::ReadListGachaItems,
PermissionsEnum::ReadDetailGachaItems,
PermissionsEnum::CreateGachaItems,
PermissionsEnum::DeleteGachaItems,
PermissionsEnum::UpdateGachaItems,
PermissionsEnum::ReadDetailGachaRolls,
PermissionsEnum::CreateGachaRolls,
PermissionsEnum::ExecuteGachaRolls,
] {
db.query("CREATE type::thing('app_permissions', $id) CONTENT $data")
.bind(("id", permission.id()))
.bind((
"data",
json!({
"name": permission.to_string(),
"is_deleted": false,
"created_at": get_iso_date(),
"updated_at": get_iso_date()
}),
))
.await?;
println!("✅ Inserted: {}", permission.to_string());
}
println!("✅ All Permissions seeded");
Ok(())
}
+72
View File
@@ -0,0 +1,72 @@
use imphnen_utils::{get_iso_date, Env};
use serde_json::json;
use std::error::Error;
use surrealdb::opt::auth::Root;
use imphnen_libs::enviroment::load_env;
use surrealdb::engine::any;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
load_env();
let env = Env::new();
let db = any::connect(&env.surrealdb_url).await?;
db.signin(Root {
username: &env.surrealdb_username,
password: &env.surrealdb_password,
})
.await?;
db.use_ns(env.surrealdb_namespace)
.use_db(env.surrealdb_dbname)
.await?;
let roles = vec![
(
"50133429-f4b1-4249-9f97-7b86e6ee9d86",
"Staf",
Some("2025-02-24T16:52:27.630453+00"),
Some("2025-02-24T16:52:27.630461+00"),
),
(
"5713cb37-dc02-4e87-8048-d7a41d352059",
"User",
None,
Some("2025-02-28T14:53:58.576688+00"),
),
(
"60f1aeb7-dad2-4e06-bcb5-be1ba510c906",
"Staff Aktivasi User",
Some("2025-02-20T02:47:09.660640+00"),
Some("2025-02-20T02:48:30.083283+00"),
),
(
"6d4fea5d-4a08-4b8a-9782-f2ab2183dcf0",
"Admin Pembayaran",
Some("2025-01-29T05:39:28.562667+00"),
Some("2025-03-12T22:56:29.597416+00"),
),
(
"f6b03f25-e416-4893-ac88-caaa690afb07",
"Admin",
None,
Some("2025-02-22T15:38:39.868306+00"),
),
];
for (id, name, _created_at, _updated_at) in roles {
db.query("CREATE type::thing('app_roles', $id) CONTENT $data")
.bind(("id", id))
.bind((
"data",
json!({
"name": name,
"permissions": [],
"is_deleted": false,
"created_at": get_iso_date(),
"updated_at": get_iso_date(),
}),
))
.await?;
println!("✅ Inserted role: {}", name);
}
println!("✅ All Roles seeded");
Ok(())
}
@@ -0,0 +1,58 @@
use imphnen_iam::{get_iso_date, make_thing, Env, PermissionsEnum};
use std::error::Error;
use surrealdb::opt::auth::Root;
use surrealdb::engine::any;
use imphnen_libs::enviroment::load_env;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
load_env();
let env = Env::new();
let db = any::connect(&env.surrealdb_url).await?;
db.signin(Root {
username: &env.surrealdb_username,
password: &env.surrealdb_password,
})
.await?;
db.use_ns(env.surrealdb_namespace)
.use_db(env.surrealdb_dbname)
.await?;
let permission_refs_admin: Vec<_> = [
PermissionsEnum::ReadListUsers,
PermissionsEnum::ReadDetailUsers,
PermissionsEnum::CreateUsers,
PermissionsEnum::DeleteUsers,
PermissionsEnum::UpdateUsers,
PermissionsEnum::ActivateUsers,
PermissionsEnum::ReadListRoles,
PermissionsEnum::ReadDetailRoles,
PermissionsEnum::CreateRoles,
PermissionsEnum::DeleteRoles,
PermissionsEnum::UpdateRoles,
PermissionsEnum::ReadListPermissions,
PermissionsEnum::ReadDetailPermissions,
PermissionsEnum::CreatePermissions,
PermissionsEnum::DeletePermissions,
PermissionsEnum::UpdatePermissions,
PermissionsEnum::CreateGachaClaims,
PermissionsEnum::ReadDetailGachaClaims,
PermissionsEnum::ReadListGachaItems,
PermissionsEnum::ReadDetailGachaItems,
PermissionsEnum::CreateGachaItems,
PermissionsEnum::DeleteGachaItems,
PermissionsEnum::UpdateGachaItems,
PermissionsEnum::ReadDetailGachaRolls,
PermissionsEnum::CreateGachaRolls,
PermissionsEnum::ExecuteGachaRolls,
]
.iter()
.map(|perm| make_thing("app_permissions", perm.id()))
.collect();
let admin_role_id = "f6b03f25-e416-4893-ac88-caaa690afb07";
db.query("UPDATE type::thing('app_roles', $role_id) SET permissions = $permissions, updated_at = $updated_at WHERE is_deleted = false")
.bind(("role_id", admin_role_id))
.bind(("permissions", permission_refs_admin))
.bind(("updated_at", get_iso_date()))
.await?;
println!("✅ All permissions successfully added to Admin role");
Ok(())
}
+68
View File
@@ -0,0 +1,68 @@
use imphnen_iam::UsersSchema;
use imphnen_libs::enviroment::load_env;
use imphnen_utils::{get_iso_date, hash_password, Env};
use std::error::Error;
use surrealdb::{opt::auth::Root, sql::Thing};
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
load_env();
let env = Env::new();
use surrealdb::engine::any;
let db = any::connect(&env.surrealdb_url).await?;
db.signin(Root {
username: &env.surrealdb_username,
password: &env.surrealdb_password,
})
.await?;
db.use_ns(env.surrealdb_namespace)
.use_db(env.surrealdb_dbname)
.await?;
let users = vec![
(
"c3b1d6a8-8d4f-4b36-b789-2e532ec7a7b2",
"admin@example.com",
"Admin",
"f6b03f25-e416-4893-ac88-caaa690afb07",
),
(
"a4d23fb5-9e31-423c-9842-fbd6e75a5298",
"staff@example.com",
"Staff",
"50133429-f4b1-4249-9f97-7b86e6ee9d86",
),
(
"d5e89c12-72af-4b1a-abc3-ff1234567890",
"user@example.com",
"User",
"5713cb37-dc02-4e87-8048-d7a41d352059",
),
];
for (id, email, fullname, role_id) in users {
let user = UsersSchema {
id: Thing::from(("app_users", id)),
fullname: fullname.into(),
email: email.into(),
password: hash_password("password").unwrap(),
avatar: None,
phone_number: "081234567890".into(),
is_active: true,
is_deleted: false,
gender: None,
birthdate: None,
role: Thing::from(("app_roles", role_id)),
created_at: get_iso_date(),
updated_at: get_iso_date(),
};
db.create::<Option<UsersSchema>>(("app_users", id))
.content(user)
.await?;
println!("✅ Inserted user: {} ({})", fullname, email);
}
println!("✅ All Users seeded");
Ok(())
}
+26
View File
@@ -0,0 +1,26 @@
use std::error::Error;
use std::process::Command;
fn run_seed(bin: &str) -> Result<(), Box<dyn Error>> {
println!("🔧 Seeding: {bin}");
let status = Command::new("cargo").args(["run", "--bin", bin]).status()?;
if !status.success() {
Err(format!("❌ Failed to run seed: {bin}").into())
} else {
Ok(())
}
}
fn main() -> Result<(), Box<dyn Error>> {
println!("🚀 Running all seeders...\n");
run_seed("seed_permissions")?;
run_seed("seed_roles")?;
run_seed("seed_roles_permissions")?;
run_seed("seed_users")?;
run_seed("seed_events")?;
println!("\n✅ All seeding completed successfully.");
Ok(())
}
+10
View File
@@ -0,0 +1,10 @@
use imphnen_gateway::gateway_service;
use imphnen_libs::axum_init;
#[tokio::main]
async fn main() {
axum_init(|surrealdb_ws, surrealdb_mem| async {
gateway_service(surrealdb_ws, surrealdb_mem).await
})
.await;
}
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "imphnen-cms"
version = "0.1.0"
edition = "2024"
[dependencies]
imphnen-iam = { version = "0.1.0", path = "../imphnen-iam" }
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" }
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
+2
View File
@@ -0,0 +1,2 @@
pub mod v1;
pub use v1::*;
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,119 @@
use super::{
events_dto::{
EventsCreateRequestDto, EventsDetailItemDto, EventsListItemDto,
EventsUpdateRequestDto,
},
events_service::EventsService,
};
use axum::extract::{Path, Query};
use axum::response::IntoResponse;
use axum::{Extension, Json};
use imphnen_libs::{
AppState, MessageResponseDto, MetaRequestDto, ResponseListSuccessDto,
ResponseSuccessDto,
};
#[utoipa::path(
get,
path = "/v1/cms/landing/events",
params(
("page" = Option<i64>, Query, description = "Page number"),
("per_page" = Option<i64>, Query, description = "Items per page"),
("search" = Option<String>, Query, description = "Search keyword"),
("sort_by" = Option<String>, Query, description = "Sort by field"),
("order" = Option<String>, Query, description = "Order ASC or DESC"),
("filter" = Option<String>, Query, description = "Filter value"),
("filter_by" = Option<String>, Query, description = "Field to filter by"),
),
responses(
(status = 200, description = "Get event list", body = ResponseListSuccessDto<Vec<EventsListItemDto>>)
),
tag = "Events"
)]
pub async fn get_event_list(
Extension(state): Extension<AppState>,
Query(meta): Query<MetaRequestDto>,
) -> impl IntoResponse {
EventsService::get_event_list(&state, meta).await
}
#[utoipa::path(
get,
path = "/v1/cms/landing/events/detail/{id}",
params(
("id" = String, Path, description = "Event ID")
),
responses(
(status = 200, description = "Get event by ID", body = ResponseSuccessDto<EventsDetailItemDto>)
),
tag = "Events"
)]
pub async fn get_event_by_id(
Extension(state): Extension<AppState>,
Path(id): Path<String>,
) -> impl IntoResponse {
EventsService::get_event_by_id(&state, id).await
}
#[utoipa::path(
post,
security(
("Bearer" = [])
),
path = "/v1/cms/landing/events/create",
request_body = EventsCreateRequestDto,
responses(
(status = 201, description = "Create new event", body = MessageResponseDto)
),
tag = "Events"
)]
pub async fn post_create_event(
Extension(state): Extension<AppState>,
Json(payload): Json<EventsCreateRequestDto>,
) -> impl IntoResponse {
EventsService::create_event(&state, payload).await
}
#[utoipa::path(
patch,
security(
("Bearer" = [])
),
path = "/v1/cms/landing/events/update/{id}",
params(
("id" = String, Path, description = "Event ID")
),
request_body = EventsUpdateRequestDto,
responses(
(status = 200, description = "Update event", body = MessageResponseDto)
),
tag = "Events"
)]
pub async fn patch_update_event(
Extension(state): Extension<AppState>,
Path(id): Path<String>,
Json(payload): Json<EventsUpdateRequestDto>,
) -> impl IntoResponse {
EventsService::update_event(&state, id, payload).await
}
#[utoipa::path(
delete,
security(
("Bearer" = [])
),
path = "/v1/cms/landing/events/delete/{id}",
params(
("id" = String, Path, description = "Event ID")
),
responses(
(status = 200, description = "Soft delete event", body = MessageResponseDto)
),
tag = "Events"
)]
pub async fn delete_event(
Extension(state): Extension<AppState>,
Path(id): Path<String>,
) -> impl IntoResponse {
EventsService::delete_event(&state, id).await
}
@@ -0,0 +1,121 @@
use chrono::{DateTime, Utc};
use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;
use utoipa::ToSchema;
use validator::Validate;
// Lazy static regex for URL validation
lazy_static! {
static ref VALID_URL_REGEX: Regex = Regex::new(r"^https?://").unwrap();
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
pub struct EventsCreateRequestDto {
#[validate(length(min = 1, message = "Name is required"))]
pub name: String,
#[validate(length(min = 1, message = "Description is required"))]
pub description: String,
#[validate(regex(
path = "VALID_URL_REGEX",
message = "Detail link must be a valid URL"
))]
pub detail_link: String,
#[validate(range(min = 0, message = "Price cannot be negative"))]
pub price: f64,
#[schema(example = "2025-09-20T13:00:00Z", value_type = String)]
pub end_date: DateTime<Utc>,
#[schema(example = "2025-09-20T13:00:00Z", value_type = String)]
pub start_date: DateTime<Utc>,
pub location: Option<String>,
pub is_online: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
pub struct EventsUpdateRequestDto {
#[validate(length(min = 1, message = "Name is required"))]
pub name: String,
#[schema(example = "2025-09-20T13:00:00Z", value_type = String)]
pub end_date: DateTime<Utc>,
#[schema(example = "2025-09-20T13:00:00Z", value_type = String)]
pub start_date: DateTime<Utc>,
pub price: f64,
pub is_online: bool,
pub description: String,
pub detail_link: String,
pub location: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct EventsListItemDto {
pub id: String,
pub name: String,
pub description: String,
pub detail_link: String,
pub price: f64,
pub is_online: bool,
pub start_date: String,
pub end_date: String,
pub created_at: String,
pub location: Option<String>,
pub is_deleted: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct EventsDetailItemDto {
pub id: String,
pub name: String,
pub description: String,
pub detail_link: String,
pub price: f64,
pub is_online: bool,
pub start_date: String,
pub end_date: String,
pub created_at: String,
pub updated_at: String,
pub location: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EventsQueryDto {
pub id: Thing,
pub name: String,
pub description: String,
pub detail_link: String,
pub price: f64,
pub is_online: bool,
pub is_deleted: bool,
pub start_date: String,
pub end_date: String,
pub created_at: String,
pub updated_at: String,
pub location: Option<String>,
}
impl EventsQueryDto {
pub fn from(self) -> EventsListItemDto {
EventsListItemDto {
id: self.id.id.to_raw(),
name: self.name,
description: self.description,
detail_link: self.detail_link,
price: self.price,
location: self.location,
is_online: self.is_online,
start_date: self.start_date,
end_date: self.end_date,
created_at: self.created_at,
is_deleted: self.is_deleted,
}
}
}
@@ -0,0 +1,113 @@
use super::{events_dto::EventsQueryDto, events_schema::EventsSchema};
use anyhow::{Result, bail};
use imphnen_libs::{AppState, MetaRequestDto, ResourceEnum, ResponseListSuccessDto};
use imphnen_utils::{DetailQueryBuilder, ListQueryBuilder, get_id, get_iso_date};
pub struct EventsRepository<'a> {
state: &'a AppState,
}
impl<'a> EventsRepository<'a> {
pub fn new(state: &'a AppState) -> Self {
Self { state }
}
pub async fn query_event_list(
&self,
meta: MetaRequestDto,
) -> Result<ResponseListSuccessDto<Vec<EventsQueryDto>>> {
let query = ListQueryBuilder::new(&ResourceEnum::Events.to_string())
.with_select_fields(vec!["*"])
.with_pagination(meta.page, Some(10))
.with_sorting(meta.sort_by.as_deref(), meta.order.as_deref())
.build();
let res: Vec<EventsQueryDto> =
self.state.surrealdb_ws.query(query).await?.take(0)?;
let data = ResponseListSuccessDto {
data: res,
meta: None,
};
Ok(data)
}
// Get event by ID
pub async fn query_event_by_id(&self, id: String) -> Result<EventsQueryDto> {
let db = &self.state.surrealdb_ws;
let builder = DetailQueryBuilder::new(ResourceEnum::Events.to_string())
.with_id(&id)
.with_select_fields(vec!["*"]);
let sql = builder.build();
let result: Option<EventsQueryDto> =
builder.apply_bindings(db.query(sql)).await?.take(0)?;
match result {
Some(event) => {
if event.is_deleted {
bail!("Event not found");
}
Ok(event)
}
None => bail!("Event not found"),
}
}
// Create new event
pub async fn query_create_event(&self, data: EventsSchema) -> Result<String> {
let db = &self.state.surrealdb_ws;
let record: Option<EventsSchema> = db
.create(ResourceEnum::Events.to_string())
.content(data)
.await?;
match record {
Some(_) => Ok("Success create event".into()),
None => bail!("Failed to create event"),
}
}
// Update existing event
pub async fn query_update_event(&self, data: EventsSchema) -> Result<String> {
let db = &self.state.surrealdb_ws;
// Cek apakah event ada
let existing = self.query_event_by_id(data.id.id.to_raw()).await?;
if existing.is_deleted {
bail!("Event already deleted");
}
// Merge field tertentu jika diperlukan
let merged = EventsSchema {
created_at: existing.created_at,
updated_at: get_iso_date(),
..data
};
let record_key = get_id(&merged.id)?;
let record: Option<EventsSchema> = db.update(record_key).merge(merged).await?;
match record {
Some(_) => Ok("Success update event".into()),
None => bail!("Failed to update event"),
}
}
// Soft delete event (mark is_deleted = true)
pub async fn query_delete_event(&self, id: String) -> Result<String> {
let db = &self.state.surrealdb_ws;
let event = self.query_event_by_id(id).await?;
if event.is_deleted {
bail!("Event not found");
}
let record_key = get_id(&event.id)?;
let record: Option<EventsSchema> = db
.update(record_key)
.merge(serde_json::json!({ "is_deleted": true }))
.await?;
match record {
Some(_) => Ok("Success delete event".into()),
None => bail!("Failed to delete event"),
}
}
}
@@ -0,0 +1,100 @@
use imphnen_libs::ResourceEnum;
use surrealdb::Uuid;
use surrealdb::sql::Thing;
use serde::{Deserialize, Serialize};
use imphnen_utils::{get_iso_date, make_thing};
use super::events_dto::{EventsCreateRequestDto, EventsQueryDto, EventsUpdateRequestDto};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EventsSchema {
pub id: Thing,
pub price: f64,
pub is_online: bool,
pub is_deleted: bool,
pub name: String,
pub end_date: String,
pub start_date: String,
pub created_at: String,
pub updated_at: String,
pub description: String,
pub detail_link: String,
pub location: Option<String>,
}
impl Default for EventsSchema {
fn default() -> Self {
Self {
id: make_thing(
&ResourceEnum::Events.to_string(),
&Uuid::new_v4().to_string(),
),
name: String::new(),
description: String::new(),
detail_link: String::new(),
price: 0.0,
location: None,
is_online: false,
is_deleted: false,
start_date: String::new(),
end_date: String::new(),
created_at: get_iso_date(),
updated_at: get_iso_date(),
}
}
}
impl EventsSchema {
pub fn from(dto: EventsQueryDto) -> Self {
Self {
id: dto.id,
name: dto.name,
description: dto.description,
detail_link: dto.detail_link,
price: dto.price,
location: dto.location,
is_online: dto.is_online,
is_deleted: false,
start_date: dto.start_date,
end_date: dto.end_date,
created_at: dto.created_at,
updated_at: dto.updated_at,
}
}
pub fn create(payload: EventsCreateRequestDto) -> Self {
Self {
id: make_thing(
&ResourceEnum::Events.to_string(),
&Uuid::new_v4().to_string(),
),
name: payload.name,
description: payload.description,
detail_link: payload.detail_link,
price: payload.price,
location: payload.location,
is_online: payload.is_online,
is_deleted: false,
end_date: payload.end_date.to_string(),
start_date: payload.start_date.to_string(),
created_at: get_iso_date(),
updated_at: get_iso_date(),
}
}
pub fn update(payload: EventsUpdateRequestDto, id: String) -> Self {
Self {
id: make_thing(&ResourceEnum::Events.to_string(), &id),
name: payload.name,
price: payload.price,
location: payload.location,
is_online: payload.is_online,
description: payload.description,
detail_link: payload.detail_link,
end_date: payload.end_date.to_string(),
start_date: payload.start_date.to_string(),
updated_at: get_iso_date(),
..Default::default()
}
}
}
@@ -0,0 +1,86 @@
use super::{
events_dto::{EventsCreateRequestDto, EventsDetailItemDto, EventsListItemDto, EventsQueryDto, EventsUpdateRequestDto},
events_repository::EventsRepository,
events_schema::EventsSchema,
};
use imphnen_libs::{AppState, MetaRequestDto, ResponseListSuccessDto, ResponseSuccessDto};
use imphnen_utils::{common_response, success_list_response, success_response, validate_request};
use axum::{http::StatusCode, response::Response};
pub struct EventsService;
impl EventsService {
pub async fn get_event_list(state: &AppState, meta: MetaRequestDto) -> Response {
let repo = EventsRepository::new(state);
match repo.query_event_list(meta).await {
Ok(data) => {
let items: Vec<EventsListItemDto> = data.data
.into_iter()
.filter(|e| !e.is_deleted)
.map(EventsQueryDto::from)
.collect();
let response = ResponseListSuccessDto {
data: items,
meta: data.meta,
};
success_list_response(response)
}
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
}
}
pub async fn get_event_by_id(state: &AppState, id: String) -> Response {
let repo = EventsRepository::new(state);
match repo.query_event_by_id(id).await {
Ok(event) if !event.is_deleted => success_response(ResponseSuccessDto {
data: EventsDetailItemDto {
id: event.id.id.to_raw(),
name: event.name,
description: event.description,
detail_link: event.detail_link,
price: event.price,
is_online: event.is_online,
start_date: event.start_date,
end_date: event.end_date,
created_at: event.created_at,
updated_at: event.updated_at,
location: event.location,
},
}),
Ok(_) => common_response(StatusCode::NOT_FOUND, "Event not found"),
Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()),
}
}
pub async fn create_event(state: &AppState, payload: EventsCreateRequestDto) -> Response {
if let Err((status, message)) = validate_request(&payload) {
return common_response(status, &message);
}
let repo = EventsRepository::new(state);
let schema = EventsSchema::create(payload);
match repo.query_create_event(schema).await {
Ok(msg) => common_response(StatusCode::CREATED, &msg),
Err(e) => common_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
}
}
pub async fn update_event(state: &AppState, id: String, payload: EventsUpdateRequestDto) -> Response {
if let Err((status, message)) = validate_request(&payload) {
return common_response(status, &message);
}
let repo = EventsRepository::new(state);
let schema = EventsSchema::update(payload, id);
match repo.query_update_event(schema).await {
Ok(msg) => common_response(StatusCode::OK, &msg),
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
}
}
pub async fn delete_event(state: &AppState, id: String) -> Response {
let repo = EventsRepository::new(state);
match repo.query_delete_event(id).await {
Ok(msg) => common_response(StatusCode::OK, &msg),
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
}
}
}
+44
View File
@@ -0,0 +1,44 @@
use axum::{
Router,
routing::{delete, get, patch, post},
};
pub mod events_controller;
pub mod events_dto;
pub mod events_repository;
pub mod events_schema;
pub mod events_service;
pub use events_controller::*;
pub use events_dto::*;
pub use events_repository::*;
pub use events_schema::*;
pub use events_service::*;
pub fn events_public_routes() -> Router {
Router::new()
.route(
"/cms/landing/events",
get(events_controller::get_event_list),
)
.route(
"/cms/landing/events/detail/{id}",
get(events_controller::get_event_by_id),
)
}
pub fn events_protected_routes() -> Router {
Router::new()
.route(
"/cms/landing/events/create",
post(events_controller::post_create_event),
)
.route(
"/cms/landing/events/update/{id}",
patch(events_controller::patch_update_event),
)
.route(
"/cms/landing/events/delete/{id}",
delete(events_controller::delete_event),
)
}
+5
View File
@@ -0,0 +1,5 @@
pub mod events;
pub mod testimonials;
pub use events::*;
pub use testimonials::*;
@@ -0,0 +1,44 @@
use axum::{
Router,
routing::{delete, get, patch, post},
};
pub mod testimonials_controller;
pub mod testimonials_dto;
pub mod testimonials_repository;
pub mod testimonials_schema;
pub mod testimonials_service;
pub use testimonials_controller::*;
pub use testimonials_dto::*;
pub use testimonials_repository::*;
pub use testimonials_schema::*;
pub use testimonials_service::*;
pub fn testimonials_public_routes() -> Router {
Router::new()
.route(
"/cms/landing/testimonials",
get(testimonials_controller::get_testimonial_list),
)
.route(
"/cms/landing/testimonials/detail/{id}",
get(testimonials_controller::get_testimonial_by_id),
)
}
pub fn testimonials_protected_routes() -> Router {
Router::new()
.route(
"/cms/landing/testimonials/create",
post(testimonials_controller::post_create_testimonial),
)
.route(
"/cms/landing/testimonials/update/{id}",
patch(testimonials_controller::patch_update_testimonial),
)
.route(
"/cms/landing/testimonials/delete/{id}",
delete(testimonials_controller::delete_testimonial),
)
}
@@ -0,0 +1,125 @@
use super::{
testimonials_dto::{
TestimonialsCreateRequestDto, TestimonialsDetailItemDto,
TestimonialsListItemDto, TestimonialsUpdateRequestDto,
},
testimonials_service::TestimonialsService,
};
use axum::extract::{Path, Query};
use axum::response::IntoResponse;
use axum::{Extension, Json};
use imphnen_iam::UsersDetailQueryDto;
use imphnen_libs::{
AppState, MessageResponseDto, MetaRequestDto, ResponseListSuccessDto,
ResponseSuccessDto,
};
#[utoipa::path(
get,
path = "/v1/cms/landing/testimonials",
params(
("page" = Option<i64>, Query, description = "Page number"),
("per_page" = Option<i64>, Query, description = "Items per page"),
("search" = Option<String>, Query, description = "Search keyword"),
("sort_by" = Option<String>, Query, description = "Sort by field"),
("order" = Option<String>, Query, description = "Order ASC or DESC"),
("filter" = Option<String>, Query, description = "Filter value"),
("filter_by" = Option<String>, Query, description = "Field to filter by"),
),
responses(
(status = 200, description = "Get testimonial list", body = ResponseListSuccessDto<Vec<TestimonialsListItemDto>>)
),
tag = "Testimonials"
)]
pub async fn get_testimonial_list(
Extension(state): Extension<AppState>,
Query(meta): Query<MetaRequestDto>,
) -> impl IntoResponse {
TestimonialsService::get_testimonial_list(&state, meta).await
}
#[utoipa::path(
get,
path = "/v1/cms/landing/testimonials/detail/{id}",
params(
("id" = String, Path, description = "Testimonial ID")
),
responses(
(status = 200, description = "Get testimonial by ID", body = ResponseSuccessDto<TestimonialsDetailItemDto>)
),
tag = "Testimonials"
)]
pub async fn get_testimonial_by_id(
Extension(state): Extension<AppState>,
Path(id): Path<String>,
) -> impl IntoResponse {
TestimonialsService::get_testimonial_by_id(&state, id).await
}
#[utoipa::path(
post,
security(
("Bearer" = [])
),
path = "/v1/cms/landing/testimonials/create",
request_body = TestimonialsCreateRequestDto,
responses(
(status = 201, description = "Create new testimonial", body = MessageResponseDto)
),
tag = "Testimonials"
)]
pub async fn post_create_testimonial(
Extension(state): Extension<AppState>,
Extension(authenticated_user): Extension<UsersDetailQueryDto>,
Json(payload): Json<TestimonialsCreateRequestDto>,
) -> impl IntoResponse {
println!("Authenticated User Now: {:?}", authenticated_user);
TestimonialsService::create_testimonial(&state, payload, &authenticated_user).await
}
#[utoipa::path(
patch,
security(
("Bearer" = [])
),
path = "/v1/cms/landing/testimonials/update/{id}",
params(
("id" = String, Path, description = "Testimonial ID")
),
request_body = TestimonialsUpdateRequestDto,
responses(
(status = 200, description = "Update testimonial", body = MessageResponseDto)
),
tag = "Testimonials"
)]
pub async fn patch_update_testimonial(
Path(id): Path<String>,
Extension(state): Extension<AppState>,
Extension(authenticated_user): Extension<UsersDetailQueryDto>,
Json(payload): Json<TestimonialsUpdateRequestDto>,
) -> impl IntoResponse {
TestimonialsService::update_testimonial(&state, id, payload, &authenticated_user)
.await
}
#[utoipa::path(
delete,
security(
("Bearer" = [])
),
path = "/v1/cms/landing/testimonials/delete/{id}",
params(
("id" = String, Path, description = "Testimonial ID")
),
responses(
(status = 200, description = "Soft delete testimonial", body = MessageResponseDto)
),
tag = "Testimonials"
)]
pub async fn delete_testimonial(
Extension(state): Extension<AppState>,
Extension(authenticated_user): Extension<UsersDetailQueryDto>,
Path(id): Path<String>,
) -> impl IntoResponse {
TestimonialsService::delete_testimonial(&state, id, &authenticated_user).await
}
@@ -0,0 +1,78 @@
use imphnen_iam::users::UsersSchema;
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;
use utoipa::ToSchema;
use validator::Validate;
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
pub struct TestimonialsCreateRequestDto {
#[validate(length(min = 1, message = "Role is required"))]
pub role: String,
#[validate(length(
min = 1,
max = 500,
message = "Content must be between 1 and 500 characters"
))]
pub content: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
pub struct TestimonialsUpdateRequestDto {
#[validate(length(min = 1, message = "Role is required"))]
pub role: String,
#[validate(length(
min = 1,
max = 500,
message = "Content must be between 1 and 500 characters"
))]
pub content: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct TestimonialsListItemDto {
pub id: String,
pub user_id: String,
pub user_fullname: String, // Assuming we'll fetch user's full name
pub role: String,
pub content: String,
pub created_at: String,
pub is_deleted: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct TestimonialsDetailItemDto {
pub id: String,
pub user_id: String,
pub user_fullname: String, // Assuming we'll fetch user's full name
pub role: String,
pub content: String,
pub created_at: String,
pub updated_at: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TestimonialsQueryDto {
pub id: Thing,
pub user: UsersSchema, // Change from Thing to UsersSchema
pub role: String,
pub content: String,
pub is_deleted: bool,
pub created_at: String,
pub updated_at: String,
}
impl TestimonialsQueryDto {
pub fn from(self) -> TestimonialsListItemDto {
TestimonialsListItemDto {
id: self.id.id.to_raw(),
user_id: self.user.id.id.to_raw(),
user_fullname: self.user.fullname, // Extract fullname from UsersSchema
role: self.role,
content: self.content,
created_at: self.created_at,
is_deleted: self.is_deleted,
}
}
}
@@ -0,0 +1,120 @@
use super::{
testimonials_dto::TestimonialsQueryDto, testimonials_schema::TestimonialsSchema,
};
use anyhow::{Result, bail};
use imphnen_libs::{AppState, MetaRequestDto, ResourceEnum, ResponseListSuccessDto};
use imphnen_utils::{DetailQueryBuilder, ListQueryBuilder, get_id, get_iso_date};
pub struct TestimonialsRepository<'a> {
state: &'a AppState,
}
impl<'a> TestimonialsRepository<'a> {
pub fn new(state: &'a AppState) -> Self {
Self { state }
}
pub async fn query_testimonial_list(
&self,
meta: MetaRequestDto,
) -> Result<ResponseListSuccessDto<Vec<TestimonialsQueryDto>>> {
let query = ListQueryBuilder::new(&ResourceEnum::Testimonials.to_string())
.with_select_fields(vec!["*", "user.* as user"]) // Select user details
.with_pagination(meta.page, Some(10))
.with_sorting(meta.sort_by.as_deref(), meta.order.as_deref())
.build();
let res: Vec<TestimonialsQueryDto> =
self.state.surrealdb_ws.query(query).await?.take(0)?;
let data = ResponseListSuccessDto {
data: res,
meta: None,
};
Ok(data)
}
pub async fn query_testimonial_by_id(
&self,
id: String,
) -> Result<TestimonialsQueryDto> {
let db = &self.state.surrealdb_ws;
let builder = DetailQueryBuilder::new(ResourceEnum::Testimonials.to_string())
.with_id(&id)
.with_select_fields(vec!["*", "user.* as user"]); // Select user details
let sql = builder.build();
let result: Option<TestimonialsQueryDto> =
builder.apply_bindings(db.query(sql)).await?.take(0)?;
match result {
Some(testimonial) => {
if testimonial.is_deleted {
bail!("Testimonial not found");
}
Ok(testimonial)
}
None => bail!("Testimonial not found"),
}
}
pub async fn query_create_testimonial(
&self,
data: TestimonialsSchema,
) -> Result<String> {
let db = &self.state.surrealdb_ws;
let record: Option<TestimonialsSchema> = db
.create(ResourceEnum::Testimonials.to_string())
.content(data)
.await?;
match record {
Some(_) => Ok("Success create testimonial".into()),
None => bail!("Failed to create testimonial"),
}
}
pub async fn query_update_testimonial(
&self,
data: TestimonialsSchema,
) -> Result<String> {
let db = &self.state.surrealdb_ws;
let existing = self.query_testimonial_by_id(data.id.id.to_raw()).await?;
if existing.is_deleted {
bail!("Testimonial already deleted");
}
let merged = TestimonialsSchema {
created_at: existing.created_at,
updated_at: get_iso_date(),
user: existing.user.id, // Preserve user ID
..data
};
let record_key = get_id(&merged.id)?;
let record: Option<TestimonialsSchema> =
db.update(record_key).merge(merged).await?;
match record {
Some(_) => Ok("Success update testimonial".into()),
None => bail!("Failed to update testimonial"),
}
}
pub async fn query_delete_testimonial(&self, id: String) -> Result<String> {
let db = &self.state.surrealdb_ws;
let testimonial = self.query_testimonial_by_id(id).await?;
if testimonial.is_deleted {
bail!("Testimonial not found");
}
let record_key = get_id(&testimonial.id)?;
let record: Option<TestimonialsSchema> = db
.update(record_key)
.merge(serde_json::json!({ "is_deleted": true }))
.await?;
match record {
Some(_) => Ok("Success delete testimonial".into()),
None => bail!("Failed to delete testimonial"),
}
}
}
@@ -0,0 +1,84 @@
use imphnen_libs::ResourceEnum;
use imphnen_utils::{get_iso_date, make_thing};
use serde::{Deserialize, Serialize};
use surrealdb::Uuid;
use surrealdb::sql::Thing;
use super::testimonials_dto::{
TestimonialsCreateRequestDto, TestimonialsQueryDto, TestimonialsUpdateRequestDto,
};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TestimonialsSchema {
pub id: Thing,
pub user: Thing, // Link to app_users table
pub role: String,
pub content: String,
pub is_deleted: bool,
pub created_at: String,
pub updated_at: String,
}
impl Default for TestimonialsSchema {
fn default() -> Self {
Self {
id: make_thing(
&ResourceEnum::Testimonials.to_string(),
&Uuid::new_v4().to_string(),
),
user: make_thing(
&ResourceEnum::Users.to_string(),
&Uuid::new_v4().to_string(), // Placeholder, will be replaced by actual user ID
),
role: String::new(),
content: String::new(),
is_deleted: false,
created_at: get_iso_date(),
updated_at: get_iso_date(),
}
}
}
impl TestimonialsSchema {
pub fn from(dto: TestimonialsQueryDto) -> Self {
Self {
id: dto.id,
user: dto.user.id,
role: dto.role,
content: dto.content,
is_deleted: dto.is_deleted,
created_at: dto.created_at,
updated_at: dto.updated_at,
}
}
pub fn create(payload: TestimonialsCreateRequestDto, user_id: &Thing) -> Self {
Self {
id: make_thing(
&ResourceEnum::Testimonials.to_string(),
&Uuid::new_v4().to_string(),
),
user: user_id.clone(),
role: payload.role,
content: payload.content,
is_deleted: false,
created_at: get_iso_date(),
updated_at: get_iso_date(),
}
}
pub fn update(
payload: TestimonialsUpdateRequestDto,
id: String,
user_id: &Thing,
) -> Self {
Self {
id: make_thing(&ResourceEnum::Testimonials.to_string(), &id),
role: payload.role,
content: payload.content,
updated_at: get_iso_date(),
user: user_id.clone(),
..Default::default()
}
}
}
@@ -0,0 +1,108 @@
use super::{
testimonials_dto::{
TestimonialsCreateRequestDto, TestimonialsDetailItemDto,
TestimonialsListItemDto, TestimonialsUpdateRequestDto,
},
testimonials_repository::TestimonialsRepository,
testimonials_schema::TestimonialsSchema,
};
use axum::{http::StatusCode, response::Response};
use imphnen_libs::{
AppState, MetaRequestDto, ResponseListSuccessDto, ResponseSuccessDto,
};
use imphnen_utils::{
common_response, success_list_response, success_response, validate_request,
};
pub struct TestimonialsService;
impl TestimonialsService {
pub async fn get_testimonial_list(
state: &AppState,
meta: MetaRequestDto,
) -> Response {
let repo = TestimonialsRepository::new(state);
match repo.query_testimonial_list(meta).await {
Ok(data) => {
let items: Vec<TestimonialsListItemDto> = data
.data
.into_iter()
.filter(|e| !e.is_deleted)
.map(|e| e.from())
.collect();
let response = ResponseListSuccessDto {
data: items,
meta: data.meta,
};
success_list_response(response)
}
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
}
}
pub async fn get_testimonial_by_id(state: &AppState, id: String) -> Response {
let repo = TestimonialsRepository::new(state);
match repo.query_testimonial_by_id(id).await {
Ok(testimonial) if !testimonial.is_deleted => {
success_response(ResponseSuccessDto {
data: TestimonialsDetailItemDto {
id: testimonial.id.id.to_raw(),
user_id: testimonial.user.id.id.to_raw(),
user_fullname: testimonial.user.fullname,
role: testimonial.role,
content: testimonial.content,
created_at: testimonial.created_at,
updated_at: testimonial.updated_at,
},
})
}
Ok(_) => common_response(StatusCode::NOT_FOUND, "Testimonial not found"),
Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()),
}
}
pub async fn create_testimonial(
state: &AppState,
payload: TestimonialsCreateRequestDto,
authenticated_user: &imphnen_iam::UsersDetailQueryDto,
) -> Response {
if let Err((status, message)) = validate_request(&payload) {
return common_response(status, &message);
}
let repo = TestimonialsRepository::new(state);
let schema = TestimonialsSchema::create(payload, &authenticated_user.id);
match repo.query_create_testimonial(schema).await {
Ok(msg) => common_response(StatusCode::CREATED, &msg),
Err(e) => common_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
}
}
pub async fn update_testimonial(
state: &AppState,
id: String,
payload: TestimonialsUpdateRequestDto,
authenticated_user: &imphnen_iam::UsersDetailQueryDto,
) -> Response {
if let Err((status, message)) = validate_request(&payload) {
return common_response(status, &message);
}
let repo = TestimonialsRepository::new(state);
let schema = TestimonialsSchema::update(payload, id, &authenticated_user.id);
match repo.query_update_testimonial(schema).await {
Ok(msg) => common_response(StatusCode::OK, &msg),
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
}
}
pub async fn delete_testimonial(
state: &AppState,
id: String,
_authenticated_user: &imphnen_iam::UsersDetailQueryDto,
) -> Response {
let repo = TestimonialsRepository::new(state);
match repo.query_delete_testimonial(id).await {
Ok(msg) => common_response(StatusCode::OK, &msg),
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
}
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod landing;
pub use landing::*;
File renamed without changes.
+24
View File
@@ -0,0 +1,24 @@
[package]
name = "imphnen-dimentorin"
version = "0.1.0"
edition = "2024"
[dependencies]
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" }
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);
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 50 of 181 files, more files were not shown because too many files have changed in this diff. Show more