Compare commits
215
Commits
main
...
submissions
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97c2fce7be | ||
|
|
98c46611fb | ||
|
|
fbfe3dcd51 | ||
|
|
5cc5a3dfe1 | ||
|
|
95ae55c9df | ||
|
|
f7ca67d720 | ||
|
|
02421e9bc2 | ||
|
|
5e2b0d3caf | ||
|
|
b9a51ce6cc | ||
|
|
d4a6c4c9ea | ||
|
|
ece6499e2b | ||
|
|
1caaa8404b | ||
|
|
cb6eef2054 | ||
|
|
e77797d7bb | ||
|
|
d5ccf4cf75 | ||
|
|
1a36698962 | ||
|
|
15a2ee1950 | ||
|
|
bef8482402 | ||
|
|
768e583f81 | ||
|
|
1ca6d8f47c | ||
|
|
3fcfb3709e | ||
|
|
6915a97d79 | ||
|
|
6f596efadd | ||
|
|
b6b5f48055 | ||
|
|
789b20c278 | ||
|
|
884b679ba8 | ||
|
|
6ef624c169 | ||
|
|
c10443f881 | ||
|
|
466ba3391a | ||
|
|
8c2527d61e | ||
|
|
018124f7b3 | ||
|
|
7749f6fdec | ||
|
|
b27e4a4404 | ||
|
|
3dde67a4ac | ||
|
|
49abcf28c3 | ||
|
|
9cecc8fa05 | ||
|
|
a32f54873b | ||
|
|
af41a98111 | ||
|
|
4c435708c0 | ||
|
|
ac462ea771 | ||
|
|
3e52223730 | ||
|
|
01e479350b | ||
|
|
b0cf9817d7 | ||
|
|
e530eba60d | ||
|
|
ef1d63e893 | ||
|
|
5859af5294 | ||
|
|
c12da948aa | ||
|
|
3cde18360c | ||
|
|
14b22328de | ||
|
|
96debc210a | ||
|
|
b80662188f | ||
|
|
7b9ce6cda1 | ||
|
|
1bb2d6a419 | ||
|
|
14690a378f | ||
|
|
17cc4ccdba | ||
|
|
1b7368a4b7 | ||
|
|
ff2e14d5f9 | ||
|
|
012aff60ef | ||
|
|
f3d88e2ac9 | ||
|
|
0e48b6755f | ||
|
|
7056f8c8a6 | ||
|
|
4ecdcd12d2 | ||
|
|
dd4a73e360 | ||
|
|
49e0a89ad1 | ||
|
|
82b85abe8f | ||
|
|
df11aa5eb8 | ||
|
|
ac44867ec1 | ||
|
|
7205afe43b | ||
|
|
b1c678c72b | ||
|
|
fa3687c6be | ||
|
|
ba46a19a5a | ||
|
|
8cdbca24ba | ||
|
|
8f981572cd | ||
|
|
39b75f410f | ||
|
|
6da1b2a7e7 | ||
|
|
acccae9f7c | ||
|
|
e7c17784cc | ||
|
|
042e0b4eff | ||
|
|
0b51a56dee | ||
|
|
cb98440b65 | ||
|
|
3c19d262f3 | ||
|
|
bdffe85544 | ||
|
|
9ed619e3fd | ||
|
|
00fd7e1907 | ||
|
|
b70126734e | ||
|
|
6af9461079 | ||
|
|
57b3662951 | ||
|
|
c0c08f2821 | ||
|
|
8402415649 | ||
|
|
01e20baa45 | ||
|
|
10dc869eaf | ||
|
|
199d0c885e | ||
|
|
89ef48a57e | ||
|
|
da1826ebba | ||
|
|
874986f1d6 | ||
|
|
bbff555a06 | ||
|
|
d3eb687a58 | ||
|
|
c5516d4006 | ||
|
|
5f51dd281d | ||
|
|
f6232832af | ||
|
|
277cf0770d | ||
|
|
650728a385 | ||
|
|
3cc3e2a7d8 | ||
|
|
39fc2f86ad | ||
|
|
7bd0ef2274 | ||
|
|
b40a430c49 | ||
|
|
44b1e09551 | ||
|
|
1b9251b2dd | ||
|
|
34b6cdabfb | ||
|
|
a25e896fa2 | ||
|
|
adf3ef8e28 | ||
|
|
ad46687f07 | ||
|
|
0ba04abee9 | ||
|
|
1e25a5b496 | ||
|
|
d7178c792d | ||
|
|
d2754de9ef | ||
|
|
cda950ed7e | ||
|
|
c6a95c1231 | ||
|
|
86116b8157 | ||
|
|
ac06224ed1 | ||
|
|
83e765f358 | ||
|
|
558e0ce568 | ||
|
|
148cf3a5cd | ||
|
|
ee9d23e01b | ||
|
|
1a2e0c58b6 | ||
|
|
e66f1f1634 | ||
|
|
6f45cf80c8 | ||
|
|
02be09506a | ||
|
|
b88ecd1361 | ||
|
|
fa435994f2 | ||
|
|
16c613d699 | ||
|
|
f598dedb3b | ||
|
|
cfa251337f | ||
|
|
cca9aa6318 | ||
|
|
f1e4e02697 | ||
|
|
a3c2715a85 | ||
|
|
89cc631fb0 | ||
|
|
36c12110c2 | ||
|
|
070060365a | ||
|
|
68e816f956 | ||
|
|
48db5f07ef | ||
|
|
a4b5d91906 | ||
|
|
9878279660 | ||
|
|
465cbee2c3 | ||
|
|
33a2ae86c6 | ||
|
|
d906fc4747 | ||
|
|
055ef6ac8e | ||
|
|
e63658c082 | ||
|
|
f4b0ed0a4a | ||
|
|
1e5ed26f76 | ||
|
|
d97095a38d | ||
|
|
e48507901e | ||
|
|
a43ef8ec04 | ||
|
|
874f25aa80 | ||
|
|
12a730a394 | ||
|
|
bdcd41f54d | ||
|
|
96fb8eb7d3 | ||
|
|
d2c333dbd6 | ||
|
|
054f0f63ff | ||
|
|
eb05ea5994 | ||
|
|
4f870ec4c9 | ||
|
|
31f9b6cfea | ||
|
|
07a93ff0b7 | ||
|
|
d2b098ed92 | ||
|
|
09a7f4371a | ||
|
|
b4bf78f0d6 | ||
|
|
de9c5a6dc1 | ||
|
|
8a661df059 | ||
|
|
c7c16d4197 | ||
|
|
3aec0346a9 | ||
|
|
8d2f720de1 | ||
|
|
4b8da68df4 | ||
|
|
6460979bab | ||
|
|
3ae9dbd717 | ||
|
|
0816566d03 | ||
|
|
a97044edfc | ||
|
|
9d92a9a136 | ||
|
|
8e12c79ea6 | ||
|
|
5777268441 | ||
|
|
34eaa418d5 | ||
|
|
77f12610dd | ||
|
|
5be737ad70 | ||
|
|
0ae1e26ba8 | ||
|
|
79721b7af6 | ||
|
|
093e290221 | ||
|
|
1e6957720e | ||
|
|
a0f263bb4f | ||
|
|
413a28c089 | ||
|
|
a90e8662e1 | ||
|
|
955d384f96 | ||
|
|
ea5ece9ab2 | ||
|
|
8ff532bb67 | ||
|
|
eb6cfd51ae | ||
|
|
11d6249007 | ||
|
|
497c250e6d | ||
|
|
2872c1827a | ||
|
|
f9c49d44b5 | ||
|
|
12043f027c | ||
|
|
0c28d34d57 | ||
|
|
271b00c2c4 | ||
|
|
b096f4129a | ||
|
|
7ab036f5f7 | ||
|
|
9ffe808797 | ||
|
|
acfb8259bc | ||
|
|
298144bd36 | ||
|
|
ff9d8cba69 | ||
|
|
8b2769706e | ||
|
|
b8606ef0bb | ||
|
|
44de53a49d | ||
|
|
9abab01b9c | ||
|
|
53414687ff | ||
|
|
d2ec21c7b1 | ||
|
|
4deee3a9cb | ||
|
|
b50ce2b060 | ||
|
|
ffc72367d5 |
+49
-4
@@ -1,4 +1,49 @@
|
|||||||
PORT=
|
# RUST_ENV : Mode environment aplikasi (misal: development, production)
|
||||||
DATABASE_URL=
|
# RUST_LOG : Level logging aplikasi Rust (misal: debug, info, warn, error)
|
||||||
ACCESS_TOKEN_SECRET=
|
# PORT : Port yang digunakan backend service
|
||||||
REFRESH_TOKEN_SECRET=
|
# SURREALDB_URL : URL koneksi ke SurrealDB (gunakan ws:// untuk WebSocket)
|
||||||
|
# SURREALDB_USERNAME : Username untuk SurrealDB
|
||||||
|
# SURREALDB_PASSWORD : Password untuk SurrealDB
|
||||||
|
# SURREALDB_NAMESPACE : Namespace SurrealDB yang digunakan
|
||||||
|
# SURREALDB_DBNAME : Nama database SurrealDB
|
||||||
|
# ACCESS_TOKEN_SECRET : Secret key untuk JWT access token (ganti dengan nilai rahasia)
|
||||||
|
# REFRESH_TOKEN_SECRET : Secret key untuk JWT refresh token (ganti dengan nilai rahasia)
|
||||||
|
# SMTP_EMAIL : Email yang digunakan untuk mengirim email (SMTP)
|
||||||
|
# SMTP_PASSWORD : Password email SMTP
|
||||||
|
# SMTP_NAME : Nama pengirim email (misal: nama aplikasi)
|
||||||
|
# SMTP_HOST : Host SMTP server (misal: smtp.gmail.com)
|
||||||
|
# REDISDB_URL : URL/host Redis database
|
||||||
|
# FE_URL : URL frontend aplikasi (misal: http://localhost)
|
||||||
|
# MINIO_ENDPOINT : Endpoint MinIO object storage
|
||||||
|
# MINIO_BUCKET_NAME : Nama bucket default MinIO
|
||||||
|
# MINIO_ACCESS_KEY : Access key MinIO atau MINIO_ROOT_USER
|
||||||
|
# MINIO_SECRET_KEY : Secret key MinIO atau MINIO_ROOT_PASSWORD
|
||||||
|
# MINIO_ROOT_USER : Username root MinIO (default: minioadmin)
|
||||||
|
# MINIO_ROOT_PASSWORD : Password root MinIO (default: minioadmin)
|
||||||
|
# GOOGLE_CLIENT_ID : Client ID OAuth Google (untuk login Google)
|
||||||
|
# GOOGLE_CLIENT_SECRET : Client Secret OAuth Google
|
||||||
|
|
||||||
|
RUST_ENV=development
|
||||||
|
RUST_LOG=debug
|
||||||
|
PORT=4099
|
||||||
|
SURREALDB_URL=ws://localhost:8000/rpc
|
||||||
|
SURREALDB_USERNAME=root
|
||||||
|
SURREALDB_PASSWORD=root
|
||||||
|
SURREALDB_NAMESPACE=test
|
||||||
|
SURREALDB_DBNAME=test
|
||||||
|
ACCESS_TOKEN_SECRET=your-access-token-secret-key-here
|
||||||
|
REFRESH_TOKEN_SECRET=your-refresh-token-secret-key-here
|
||||||
|
SMTP_EMAIL=your-email@example.com
|
||||||
|
SMTP_PASSWORD=your-smtp-password
|
||||||
|
SMTP_NAME="Your App Name"
|
||||||
|
SMTP_HOST=smtp.gmail.com
|
||||||
|
REDISDB_URL=localhost
|
||||||
|
FE_URL=http://localhost
|
||||||
|
MINIO_ENDPOINT=http://localhost:9000
|
||||||
|
MINIO_BUCKET_NAME=default_bucket
|
||||||
|
MINIO_ACCESS_KEY=minioadmin
|
||||||
|
MINIO_SECRET_KEY=minioadmin
|
||||||
|
MINIO_SECURE=false
|
||||||
|
|
||||||
|
GOOGLE_CLIENT_ID="your_google_client_id"
|
||||||
|
GOOGLE_CLIENT_SECRET="your_google_client_secret"
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
# Nix
|
# Nix
|
||||||
/.direnv
|
/.direnv
|
||||||
|
/Cargo.nix
|
||||||
|
|
||||||
# Environment
|
# Environment
|
||||||
.envrc
|
.envrc
|
||||||
@@ -12,3 +13,4 @@
|
|||||||
.env.development
|
.env.development
|
||||||
.env.staging
|
.env.staging
|
||||||
.env.production
|
.env.production
|
||||||
|
**/**.log
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/cache
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby)
|
||||||
|
# * For C, use cpp
|
||||||
|
# * For JavaScript, use typescript
|
||||||
|
# Special requirements:
|
||||||
|
# * csharp: Requires the presence of a .sln file in the project folder.
|
||||||
|
language: rust
|
||||||
|
|
||||||
|
# the encoding used by text files in the project
|
||||||
|
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
|
||||||
|
encoding: "utf-8"
|
||||||
|
|
||||||
|
# whether to use the project's gitignore file to ignore files
|
||||||
|
# Added on 2025-04-07
|
||||||
|
ignore_all_files_in_gitignore: true
|
||||||
|
# list of additional paths to ignore
|
||||||
|
# same syntax as gitignore, so you can use * and **
|
||||||
|
# Was previously called `ignored_dirs`, please update your config if you are using that.
|
||||||
|
# Added (renamed) on 2025-04-07
|
||||||
|
ignored_paths: []
|
||||||
|
|
||||||
|
# whether the project is in read-only mode
|
||||||
|
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
||||||
|
# Added on 2025-04-18
|
||||||
|
read_only: false
|
||||||
|
|
||||||
|
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
|
||||||
|
# Below is the complete list of tools for convenience.
|
||||||
|
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||||
|
# execute `uv run scripts/print_tool_overview.py`.
|
||||||
|
#
|
||||||
|
# * `activate_project`: Activates a project by name.
|
||||||
|
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
||||||
|
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
||||||
|
# * `delete_lines`: Deletes a range of lines within a file.
|
||||||
|
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
|
||||||
|
# * `execute_shell_command`: Executes a shell command.
|
||||||
|
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
|
||||||
|
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
|
||||||
|
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
|
||||||
|
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
||||||
|
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
||||||
|
# * `initial_instructions`: Gets the initial instructions for the current project.
|
||||||
|
# Should only be used in settings where the system prompt cannot be set,
|
||||||
|
# e.g. in clients you have no control over, like Claude Desktop.
|
||||||
|
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
||||||
|
# * `insert_at_line`: Inserts content at a given line in a file.
|
||||||
|
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
||||||
|
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
||||||
|
# * `list_memories`: Lists memories in Serena's project-specific memory store.
|
||||||
|
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
||||||
|
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
|
||||||
|
# * `read_file`: Reads a file within the project directory.
|
||||||
|
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
|
||||||
|
# * `remove_project`: Removes a project from the Serena configuration.
|
||||||
|
# * `replace_lines`: Replaces a range of lines within a file with new content.
|
||||||
|
# * `replace_symbol_body`: Replaces the full definition of a symbol.
|
||||||
|
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
|
||||||
|
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
||||||
|
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
|
||||||
|
# * `switch_modes`: Activates modes by providing a list of their names
|
||||||
|
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
|
||||||
|
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
|
||||||
|
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
|
||||||
|
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
|
||||||
|
excluded_tools: []
|
||||||
|
|
||||||
|
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||||
|
# (contrary to the memories, which are loaded on demand).
|
||||||
|
initial_prompt: ""
|
||||||
|
|
||||||
|
project_name: "imphnen-backend-service"
|
||||||
Generated
+3882
-1009
File diff suppressed because it is too large
Load Diff
+81
-15
@@ -1,21 +1,87 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "imphnen-cms-be"
|
resolver = "2"
|
||||||
version = "0.1.0"
|
members = [
|
||||||
edition = "2021"
|
"tests",
|
||||||
|
"imphnen-entities", # Most basic - core data structures
|
||||||
|
"imphnen-libs", # Depends on entities
|
||||||
|
"imphnen-utils", # Depends on libs and entities
|
||||||
|
"imphnen-middleware",# Utility for permissions
|
||||||
|
"imphnen-iam", # Core auth service, depends on libs, utils, entities
|
||||||
|
"imphnen-cms", # Content management, depends on core services
|
||||||
|
"imphnen-gacha", # Game mechanics, depends on core services
|
||||||
|
"imphnen-dimentorin",# Learning platform, depends on core services
|
||||||
|
"imphnen-hackathon", # Hackathon service, depends on core services
|
||||||
|
"imphnen-gateway", # API gateway, depends on all services
|
||||||
|
"imphnen-backend", # Main application, depends on all services
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
axum = { version = "0.8.1", features = ["multipart"] }
|
[workspace.dependencies]
|
||||||
log = "0.4.25"
|
async-trait = "0.1.83"
|
||||||
sea-orm = { version = "1.1.4", features = ["sqlx-postgres", "macros", "with-json", "with-uuid", "runtime-tokio", "runtime-tokio-native-tls"] }
|
oauth2 = "5.0.0"
|
||||||
serde = { version = "1.0.217", features = ["derive"] }
|
reqwest = { version = "0.12.23", features = ["json"] }
|
||||||
serde_json = "1.0.138"
|
serde_json = "1.0.142"
|
||||||
tokio = { version = "1.43.0", features = ["full"] }
|
axum = { version = "0.8.4", features = ["multipart"] }
|
||||||
|
log = "0.4.27"
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
tokio = { version = "1.47.1", features = ["full"] }
|
||||||
argon2 = { version = "0.5.3", features = ["password-hash"] }
|
argon2 = { version = "0.5.3", features = ["password-hash"] }
|
||||||
jsonwebtoken = "9.3.1"
|
jsonwebtoken = "9.3.1"
|
||||||
chrono = "0.4.39"
|
chrono = "0.4.41"
|
||||||
utoipa = { version = "5.3.1", features = ["axum_extras"] }
|
utoipa = { version = "5.4.0", features = ["axum_extras"] }
|
||||||
utoipa-swagger-ui = { version = "9.0.0", features = ["axum"] }
|
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
|
||||||
redis = "0.28.2"
|
lettre = { version = "0.11.18", features = ["tokio1-native-tls"] }
|
||||||
|
surrealdb = { version = "2.3.7", features = ["kv-mem", "kv-fdb"] }
|
||||||
|
thiserror = "2.0.14"
|
||||||
|
anyhow = "1.0.99"
|
||||||
|
rand = { version = "0.9.2", features = ["std", "alloc"] }
|
||||||
|
rand_distr = "0.5.1"
|
||||||
|
tower-http = { version = "0.6.6", features = ["cors", "trace"] }
|
||||||
|
http-body-util = "0.1.3"
|
||||||
|
validator = { version = "0.20.0", features = ["derive"] }
|
||||||
|
lazy_static = "1.5.0"
|
||||||
|
regex = "1.11.1"
|
||||||
|
axum-test = "17.3.0"
|
||||||
|
axum-extra = { version = "0.10.1", features = ["typed-header"] }
|
||||||
|
fancy-regex = "0.16.1"
|
||||||
|
futures = "0.3.31"
|
||||||
|
tower = "0.5.2"
|
||||||
|
env_logger = "0.11.8"
|
||||||
|
tracing = "0.1.41"
|
||||||
|
uuid = { version = "1.18.0", features = ["v4", "fast-rng", "serde"] }
|
||||||
|
strum = { version = "0.27.2", features = ["derive"] }
|
||||||
|
strum_macros = "0.27.2"
|
||||||
|
base64 = "0.22.1"
|
||||||
|
sha2 = "0.10.9"
|
||||||
|
hmac = "0.12"
|
||||||
|
hex = "0.4"
|
||||||
|
urlencoding = "2.1"
|
||||||
|
hyper = "1.6.0"
|
||||||
|
hyper-util = "0.1.16"
|
||||||
|
minio = "0.3.0"
|
||||||
|
|
||||||
|
|
||||||
|
tokio-test = "0.4.4"
|
||||||
|
mockall = "0.13.1"
|
||||||
|
once_cell = "1.21.3"
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
|
tokio-tungstenite = "0.27"
|
||||||
|
url = "2.5"
|
||||||
|
futures-util = "0.3"
|
||||||
|
http = "1.3"
|
||||||
|
tests = { path = "./tests" }
|
||||||
|
imphnen-iam = { path = "./imphnen-iam" }
|
||||||
|
imphnen-cms = { path = "./imphnen-cms" }
|
||||||
|
imphnen-libs = { path = "./imphnen-libs" }
|
||||||
|
imphnen-utils = { path = "./imphnen-utils" }
|
||||||
|
imphnen-gacha = { path = "./imphnen-gacha" }
|
||||||
|
imphnen-gateway = { path = "./imphnen-gateway" }
|
||||||
|
imphnen-backend = { path = "./imphnen-backend" }
|
||||||
|
imphnen-entities = { path = "./imphnen-entities" }
|
||||||
|
imphnen-dimentorin = { path = "./imphnen-dimentorin" }
|
||||||
|
imphnen-hackathon = { path = "./imphnen-hackathon" }
|
||||||
|
imphnen-middleware = { path = "./imphnen-middleware" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = "fat"
|
lto = "fat"
|
||||||
|
|||||||
+37
-12
@@ -1,22 +1,47 @@
|
|||||||
FROM rust:1.83-slim-bullseye AS builder
|
FROM rust:1.86-alpine AS builder
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apk add --no-cache \
|
||||||
build-essential \
|
curl \
|
||||||
libssl-dev \
|
musl-dev \
|
||||||
pkg-config \
|
openssl-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
openssl-libs-static \
|
||||||
|
pkgconfig
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY Cargo.toml Cargo.lock ./
|
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"]
|
||||||
@@ -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)._
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
@@ -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"
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Script deploy semua binary hasil build Rust (untuk Linux)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
REMOTE_USER="asephs"
|
||||||
|
REMOTE_HOST="70.153.9.42"
|
||||||
|
REMOTE_PATH="/home/asephs/imphnen-backend-service"
|
||||||
|
|
||||||
|
# Build release binary
|
||||||
|
echo "🔧 Building Rust project..."
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
# Filter hanya file executable tanpa ekstensi .exe, .rlib, atau .d
|
||||||
|
BINARIES=$(find target/release -maxdepth 1 -type f ! -name "*.exe" ! -name "*.rlib" ! -name "*.d")
|
||||||
|
|
||||||
|
if [ -z "$BINARIES" ]; then
|
||||||
|
echo "❌ Tidak ada binary Linux (.exe/.rlib/.d diabaikan)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Upload semua binary yang valid ke server
|
||||||
|
echo "🚀 Mengirim binary ke server..."
|
||||||
|
rsync -avz --compress-level=9 --progress \
|
||||||
|
$BINARIES \
|
||||||
|
$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH/target/release/
|
||||||
|
|
||||||
|
# Jalankan ulang service utama di VPS
|
||||||
|
echo "♻️ Restart service utama di server..."
|
||||||
|
ssh $REMOTE_USER@$REMOTE_HOST << 'EOF'
|
||||||
|
set -e
|
||||||
|
cd /home/asephs/imphnen-backend-service
|
||||||
|
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
if [ -f ~/.bashrc ]; then
|
||||||
|
source ~/.bashrc
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pastikan semua binary bisa dieksekusi
|
||||||
|
chmod +x target/release/*
|
||||||
|
|
||||||
|
# Restart service utama (misalnya api.d)
|
||||||
|
if pm2 list | grep -q api; then
|
||||||
|
pm2 restart api --update-env
|
||||||
|
else
|
||||||
|
pm2 start target/release/api.d --name api
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "✅ Deploy semua binary Linux selesai ke $REMOTE_HOST:$REMOTE_PATH"
|
||||||
@@ -6,3 +6,27 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "${PORT}:${PORT}"
|
- "${PORT}:${PORT}"
|
||||||
env_file: ".env"
|
env_file: ".env"
|
||||||
|
depends_on:
|
||||||
|
- surrealdb
|
||||||
|
|
||||||
|
surrealdb:
|
||||||
|
image: surrealdb/surrealdb:latest
|
||||||
|
command: start --log trace --user root --pass root
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
|
||||||
|
minio:
|
||||||
|
image: minio/minio:latest
|
||||||
|
container_name: minio
|
||||||
|
ports:
|
||||||
|
- "9000:9000"
|
||||||
|
- "9001:9001"
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: minioadmin
|
||||||
|
MINIO_ROOT_PASSWORD: minioadmin
|
||||||
|
command: server /data --console-address ":9001"
|
||||||
|
volumes:
|
||||||
|
- minio_data:/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
minio_data:
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 351 KiB |
Generated
+27
@@ -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
|
||||||
|
}
|
||||||
@@ -1,24 +1,32 @@
|
|||||||
{
|
{
|
||||||
description = "IMPHNEN CMS API Nix Flake";
|
description = "IMPHNEN Backend Service Nix Flake";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = {
|
outputs = {
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
}: let
|
}: let
|
||||||
supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-darwin" "aarch64-linux"];
|
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;
|
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
||||||
pkgsFor = nixpkgs.legacyPackages;
|
|
||||||
in {
|
in {
|
||||||
packages = forAllSystems (system: {
|
packages = forAllSystems (system: {
|
||||||
default = pkgsFor.${system}.callPackage ./default.nix {};
|
default = (pkgsFor system).callPackage ./default.nix {};
|
||||||
});
|
});
|
||||||
devShells = forAllSystems (system: {
|
devShells = forAllSystems (system: {
|
||||||
default = pkgsFor.${system}.callPackage ./shell.nix {};
|
default = (pkgsFor system).callPackage ./shell.nix {};
|
||||||
});
|
});
|
||||||
dockerImages = forAllSystems (system: {
|
dockerImages = forAllSystems (system: {
|
||||||
tryOutApi = pkgsFor.${system}.callPackage ./docker.nix {};
|
tryOutApi = (pkgsFor system).callPackage ./docker.nix {};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
[package]
|
||||||
|
name = "imphnen-backend"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "api"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "clear_db_test"
|
||||||
|
path = "src/bin/clear_db_test.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "seeder"
|
||||||
|
path = "src/bin/seeder.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "seed_events"
|
||||||
|
path = "src/bin/seed_events.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "seed_gacha_rolls"
|
||||||
|
path = "src/bin/seed_gacha_rolls.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "seed_mentor_user"
|
||||||
|
path = "src/bin/seed_mentor_user.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "seed_permissions"
|
||||||
|
path = "src/bin/seed_permissions.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "seed_roles"
|
||||||
|
path = "src/bin/seed_roles.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "seed_roles_permissions"
|
||||||
|
path = "src/bin/seed_roles_permissions.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "seed_teams"
|
||||||
|
path = "src/bin/seed_teams.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "seed_hackathons"
|
||||||
|
path = "src/bin/seed_hackathons.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "seed_test_data"
|
||||||
|
path = "src/bin/seed_test_data.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
imphnen-libs.workspace = true
|
||||||
|
imphnen-utils.workspace = true
|
||||||
|
imphnen-gateway.workspace = true
|
||||||
|
imphnen-entities.workspace = true
|
||||||
|
imphnen-iam.workspace = true
|
||||||
|
imphnen-cms.workspace = true
|
||||||
|
imphnen-gacha.workspace = true
|
||||||
|
imphnen-dimentorin.workspace = true
|
||||||
|
imphnen-hackathon.workspace = true
|
||||||
|
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
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
uuid.workspace=true
|
||||||
|
tokio-tungstenite.workspace = true
|
||||||
|
url.workspace = true
|
||||||
|
futures-util.workspace = true
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
|
||||||
|
use url::Url;
|
||||||
|
use futures_util::{StreamExt, SinkExt};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
// Menentukan kredensial dan detail koneksi secara langsung sebagai string statis
|
||||||
|
static SURREALDB_URL_WS: &str = "ws://localhost:8000/rpc";
|
||||||
|
static SURREALDB_USERNAME: &str = "root";
|
||||||
|
static SURREALDB_PASSWORD: &str = "root";
|
||||||
|
static SURREALDB_NAMESPACE: &str = "test";
|
||||||
|
static SURREALDB_DBNAME: &str = "test";
|
||||||
|
|
||||||
|
// Daftar tabel sebagai variabel static yang tidak dapat diubah
|
||||||
|
static TABLES_TO_CLEAR: &[&str] = &[
|
||||||
|
"app_events", "users", "roles", "permissions", "gacha_rolls",
|
||||||
|
"mentor_users", "gacha_claims", "gacha_credits", "gacha_items",
|
||||||
|
"mentor_profiles", "roles_permissions", "testimonials",
|
||||||
|
];
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Tidak perlu memuat env lagi, karena kita menggunakan nilai hardcoded
|
||||||
|
// imphnen_libs::enviroment::load_env(); // Baris ini tidak lagi dibutuhkan
|
||||||
|
// let env = Env::new(); // Baris ini tidak lagi dibutuhkan
|
||||||
|
|
||||||
|
println!("DEBUG: URL WS: {}", SURREALDB_URL_WS);
|
||||||
|
println!("DEBUG: Username: {}", SURREALDB_USERNAME);
|
||||||
|
println!("DEBUG: Namespace: {}", SURREALDB_NAMESPACE);
|
||||||
|
println!("DEBUG: Database: {}", SURREALDB_DBNAME);
|
||||||
|
|
||||||
|
let url = Url::parse(SURREALDB_URL_WS)?; // Menggunakan SURREALDB_URL_WS statis
|
||||||
|
|
||||||
|
let (ws_stream, _) = connect_async(url.as_str()).await?;
|
||||||
|
let (mut write, mut read) = ws_stream.split();
|
||||||
|
|
||||||
|
// Authenticate (signin)
|
||||||
|
let signin_query = json!({
|
||||||
|
"method": "signin",
|
||||||
|
"params": [{
|
||||||
|
"user": SURREALDB_USERNAME, // Menggunakan SURREALDB_USERNAME statis
|
||||||
|
"pass": SURREALDB_PASSWORD, // Menggunakan SURREALDB_PASSWORD statis
|
||||||
|
}],
|
||||||
|
"id": 1
|
||||||
|
}).to_string();
|
||||||
|
println!("DEBUG: Sending signin query: {}", signin_query);
|
||||||
|
write.send(Message::Text(signin_query.into())).await?;
|
||||||
|
|
||||||
|
let signin_response = read.next().await.ok_or("Failed to read signin response")?;
|
||||||
|
let signin_response_msg = signin_response?;
|
||||||
|
let signin_response_str = signin_response_msg.to_text()?;
|
||||||
|
println!("DEBUG: Signin response: {}", signin_response_str);
|
||||||
|
if signin_response_str.contains("\"error\":") {
|
||||||
|
return Err(format!("Signin failed: {}", signin_response_str).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use namespace and database
|
||||||
|
let use_query = json!({
|
||||||
|
"method": "use",
|
||||||
|
"params": [SURREALDB_NAMESPACE, SURREALDB_DBNAME], // Menggunakan NS & DB statis
|
||||||
|
"id": 2
|
||||||
|
}).to_string();
|
||||||
|
println!("DEBUG: Sending use query: {}", use_query);
|
||||||
|
write.send(Message::Text(use_query.into())).await?;
|
||||||
|
|
||||||
|
let use_response = read.next().await.ok_or("Failed to read use response")?;
|
||||||
|
let use_response_msg = use_response?;
|
||||||
|
let use_response_str = use_response_msg.to_text()?;
|
||||||
|
println!("DEBUG: Use response: {}", use_response_str);
|
||||||
|
if use_response_str.contains("\"error\":") {
|
||||||
|
return Err(format!("USE command failed: {}", use_response_str).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("INFO: Attempting to clear database tables via WebSocket...");
|
||||||
|
|
||||||
|
let mut all_clear = true;
|
||||||
|
for (i, table) in TABLES_TO_CLEAR.iter().enumerate() {
|
||||||
|
let remove_query = format!("REMOVE TABLE {};", table);
|
||||||
|
let query_json = json!({
|
||||||
|
"method": "query",
|
||||||
|
"params": [remove_query],
|
||||||
|
"id": i + 3
|
||||||
|
}).to_string();
|
||||||
|
|
||||||
|
println!("DEBUG: Attempting REMOVE TABLE {}: {}", table, query_json);
|
||||||
|
write.send(Message::Text(query_json.into())).await?;
|
||||||
|
let response_result = read.next().await.ok_or("Stream ended unexpectedly")?;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
match response_result {
|
||||||
|
Ok(msg) => {
|
||||||
|
let response_str = msg.to_text()?;
|
||||||
|
if response_str.contains("\"error\":") {
|
||||||
|
println!("WARN: Failed to REMOVE TABLE {}: {}. Attempting DELETE type::{}.", table, response_str, table);
|
||||||
|
let delete_all_query = format!("DELETE FROM {};", table);
|
||||||
|
let delete_all_json = json!({
|
||||||
|
"method": "query",
|
||||||
|
"params": [delete_all_query],
|
||||||
|
"id": i + 300
|
||||||
|
}).to_string();
|
||||||
|
|
||||||
|
println!("DEBUG: Attempting DELETE {}: {}", table, delete_all_json);
|
||||||
|
write.send(Message::Text(delete_all_json.into())).await?;
|
||||||
|
let delete_response_result = read.next().await.ok_or("Stream ended unexpectedly during DELETE type::")?;
|
||||||
|
|
||||||
|
match delete_response_result {
|
||||||
|
Ok(delete_msg) => {
|
||||||
|
let delete_response_str = delete_msg.to_text()?;
|
||||||
|
if delete_response_str.contains("\"error\":") {
|
||||||
|
println!("ERROR: Failed to DELETE type::{} : {}", table, delete_response_str);
|
||||||
|
all_clear = false;
|
||||||
|
} else {
|
||||||
|
println!("INFO: Successfully DELETED type:: table: {}", table);
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(delete_e) => {
|
||||||
|
println!("ERROR: Error receiving response for DELETE type:: table {}: {}", table, delete_e);
|
||||||
|
all_clear = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("INFO: Successfully REMOVED TABLE: {}", table);
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("ERROR: Error receiving response for REMOVE TABLE {}: {}", table, e);
|
||||||
|
all_clear = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if table is empty after deletion attempt
|
||||||
|
let select_query = format!("SELECT * FROM {} LIMIT 1;", table);
|
||||||
|
let select_json = json!({
|
||||||
|
"method": "query",
|
||||||
|
"params": [select_query],
|
||||||
|
"id": i + 1000
|
||||||
|
}).to_string();
|
||||||
|
write.send(Message::Text(select_json.into())).await?;
|
||||||
|
let select_response_result = read.next().await.ok_or("Stream ended unexpectedly during SELECT check")?;
|
||||||
|
match select_response_result {
|
||||||
|
Ok(select_msg) => {
|
||||||
|
let select_response_str = select_msg.to_text()?;
|
||||||
|
if select_response_str.contains("does not exist") {
|
||||||
|
println!("CHECK: Table '{}' does not exist after clear attempt (success).", table);
|
||||||
|
} else if select_response_str.contains("\"result\":[]") || select_response_str.contains("\"result\":[[]]") {
|
||||||
|
println!("CHECK: Table '{}' is empty after clear attempt.", table);
|
||||||
|
} else {
|
||||||
|
println!("WARNING: Table '{}' is NOT empty after clear attempt! Response: {}", table, select_response_str);
|
||||||
|
all_clear = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("ERROR: Error receiving response for SELECT check on table {}: {}", table, e);
|
||||||
|
all_clear = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("INFO: Database clearing complete.");
|
||||||
|
|
||||||
|
if !all_clear {
|
||||||
|
eprintln!("ERROR: One or more tables could not be cleared. Check logs for details.");
|
||||||
|
return Err("Database clearing failed for one or more tables.".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
use imphnen_libs::jsonwebtoken::encode_access_token;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
if args.len() < 2 {
|
||||||
|
eprintln!("Usage: mk_token <email_or_sub>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
let sub = args[1].clone();
|
||||||
|
// Use sub as both sub and user_id
|
||||||
|
match encode_access_token(sub.clone(), sub.clone()) {
|
||||||
|
Ok(token) => println!("{}", token),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to generate token: {:?}", e);
|
||||||
|
std::process::exit(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
use imphnen_cms::v1::landing::events::events_schema::EventsSchema;
|
||||||
|
use imphnen_utils::{get_iso_date};
|
||||||
|
use std::error::Error;
|
||||||
|
use surrealdb::engine::any;
|
||||||
|
use surrealdb::{opt::auth::Root, sql::Thing, Uuid}; // Added Uuid
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let env = &imphnen_libs::environment::ENV;
|
||||||
|
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.clone())
|
||||||
|
.use_db(env.surrealdb_dbname.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let events = vec![
|
||||||
|
(
|
||||||
|
"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",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"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",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"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",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"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 (
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
detail_link,
|
||||||
|
price,
|
||||||
|
location,
|
||||||
|
is_online,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
) in events
|
||||||
|
{
|
||||||
|
let uuid = Uuid::new_v4().to_string(); // Generate new UUID
|
||||||
|
let event = EventsSchema {
|
||||||
|
id: Thing::from(("app_events", uuid.as_str())), // Use generated UUID
|
||||||
|
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", uuid.as_str())) // Use generated UUID
|
||||||
|
.content(event)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"✅ Inserted event: {} ({})",
|
||||||
|
name,
|
||||||
|
if is_online { "Online" } else { "In-person" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("✅ All Events seeded");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use imphnen_utils::{get_iso_date};
|
||||||
|
use std::error::Error;
|
||||||
|
use surrealdb::opt::auth::Root;
|
||||||
|
use surrealdb::sql::Thing;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let env = &imphnen_libs::environment::ENV;
|
||||||
|
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.clone())
|
||||||
|
.use_db(env.surrealdb_dbname.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
db.query("DELETE type::thing('app_gacha_items', $id)")
|
||||||
|
.bind(("id", "1"))
|
||||||
|
.await?;
|
||||||
|
db.query("DELETE type::thing('app_gacha_rolls', $id)")
|
||||||
|
.bind(("id", "test-gacha-roll-001"))
|
||||||
|
.await?;
|
||||||
|
let gacha_item_id = "1";
|
||||||
|
db.query("CREATE type::thing('app_gacha_items', $id) SET name = $name, image_url = $image_url, is_deleted = $is_deleted, created_at = $created_at, updated_at = $updated_at")
|
||||||
|
.bind(("id", gacha_item_id))
|
||||||
|
.bind(("name", "Test Gacha Item"))
|
||||||
|
.bind(("image_url", "https://example.com/gacha_item.png"))
|
||||||
|
.bind(("is_deleted", false))
|
||||||
|
.bind(("created_at", get_iso_date()))
|
||||||
|
.bind(("updated_at", get_iso_date()))
|
||||||
|
.await?;
|
||||||
|
println!("Gacha Item seeded successfully!");
|
||||||
|
|
||||||
|
let gacha_roll_id = "test-gacha-roll-001";
|
||||||
|
db.query("CREATE type::thing('app_gacha_rolls', $id) SET item = $item, quantity = $quantity, weight = $weight, is_deleted = $is_deleted, created_at = $created_at, updated_at = $updated_at")
|
||||||
|
.bind(("id", gacha_roll_id))
|
||||||
|
.bind(("item", Thing::from(("app_gacha_items", gacha_item_id))))
|
||||||
|
.bind(("quantity", 10))
|
||||||
|
.bind(("weight", 1.0))
|
||||||
|
.bind(("is_deleted", false))
|
||||||
|
.bind(("created_at", get_iso_date()))
|
||||||
|
.bind(("updated_at", get_iso_date()))
|
||||||
|
.await?;
|
||||||
|
println!("Gacha Roll seeded successfully!");
|
||||||
|
|
||||||
|
println!("✅ Gacha items and rolls seeded.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,368 @@
|
|||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use imphnen_hackathon::v1::hackathon::hackathon_schema::{
|
||||||
|
HackathonSchema, HackathonEventsSchema, HackathonTimelineSchema, HackathonSubmissionsSchema,
|
||||||
|
HackathonStatus, HackathonEventType, HackathonPhase, SubmissionStatus, Prize
|
||||||
|
};
|
||||||
|
use imphnen_utils::get_iso_date;
|
||||||
|
use std::error::Error;
|
||||||
|
use surrealdb::{opt::auth::Root, sql::Thing};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let env = &imphnen_libs::environment::ENV;
|
||||||
|
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.clone())
|
||||||
|
.use_db(env.surrealdb_dbname.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Sample hackathon data
|
||||||
|
let hackathons = vec![
|
||||||
|
(
|
||||||
|
"hackathon-001",
|
||||||
|
"AI Innovation Challenge 2025",
|
||||||
|
"Build the next generation of AI-powered applications that solve real-world problems.",
|
||||||
|
"2025-10-15T09:00:00Z",
|
||||||
|
"2025-10-17T18:00:00Z",
|
||||||
|
"2025-10-01T23:59:59Z",
|
||||||
|
Some(100),
|
||||||
|
HackathonStatus::RegistrationOpen,
|
||||||
|
Some("Artificial Intelligence & Machine Learning".to_string()),
|
||||||
|
Some("1. All code must be original\n2. Teams can have 2-5 members\n3. Projects must use AI/ML technologies".to_string()),
|
||||||
|
Some(vec![
|
||||||
|
Prize { position: 1, title: "Grand Prize".to_string(), description: Some("Winner gets full scholarship".to_string()), value: Some("$10,000".to_string()) },
|
||||||
|
Prize { position: 2, title: "Second Place".to_string(), description: Some("Runner-up prize".to_string()), value: Some("$5,000".to_string()) },
|
||||||
|
Prize { position: 3, title: "Third Place".to_string(), description: Some("Third place prize".to_string()), value: Some("$2,500".to_string()) },
|
||||||
|
]),
|
||||||
|
vec!["c3b1d6a8-8d4f-4b36-b789-2e532ec7a7b2".to_string()], // admin user
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"hackathon-002",
|
||||||
|
"Green Tech Hackathon",
|
||||||
|
"Develop sustainable technology solutions for environmental challenges.",
|
||||||
|
"2025-11-20T10:00:00Z",
|
||||||
|
"2025-11-22T17:00:00Z",
|
||||||
|
"2025-11-05T23:59:59Z",
|
||||||
|
Some(75),
|
||||||
|
HackathonStatus::Draft,
|
||||||
|
Some("Sustainability & Green Technology".to_string()),
|
||||||
|
Some("Focus on renewable energy, waste reduction, and environmental monitoring.".to_string()),
|
||||||
|
Some(vec![
|
||||||
|
Prize { position: 1, title: "Eco Champion".to_string(), description: Some("Best environmental impact".to_string()), value: Some("$7,500".to_string()) },
|
||||||
|
Prize { position: 2, title: "Innovation Award".to_string(), description: Some("Most innovative solution".to_string()), value: Some("$3,500".to_string()) },
|
||||||
|
]),
|
||||||
|
vec!["c3b1d6a8-8d4f-4b36-b789-2e532ec7a7b2".to_string()],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Sample hackathon events
|
||||||
|
let hackathon_events = vec![
|
||||||
|
(
|
||||||
|
"hackathon-001",
|
||||||
|
"event-001",
|
||||||
|
"Opening Ceremony",
|
||||||
|
Some("Welcome and kickoff event for the AI Innovation Challenge".to_string()),
|
||||||
|
HackathonEventType::Ceremony,
|
||||||
|
"2025-10-15T09:00:00Z",
|
||||||
|
"2025-10-15T10:00:00Z",
|
||||||
|
Some("Main Auditorium".to_string()),
|
||||||
|
None,
|
||||||
|
Some(150),
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"hackathon-001",
|
||||||
|
"event-002",
|
||||||
|
"AI Workshop: Getting Started",
|
||||||
|
Some("Introduction to AI frameworks and tools".to_string()),
|
||||||
|
HackathonEventType::Workshop,
|
||||||
|
"2025-10-15T14:00:00Z",
|
||||||
|
"2025-10-15T16:00:00Z",
|
||||||
|
None,
|
||||||
|
Some("https://zoom.us/meeting/ai-workshop".to_string()),
|
||||||
|
Some(80),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"hackathon-001",
|
||||||
|
"event-003",
|
||||||
|
"Judging Session",
|
||||||
|
Some("Final project presentations and judging".to_string()),
|
||||||
|
HackathonEventType::Judging,
|
||||||
|
"2025-10-17T14:00:00Z",
|
||||||
|
"2025-10-17T17:00:00Z",
|
||||||
|
Some("Innovation Lab".to_string()),
|
||||||
|
None,
|
||||||
|
Some(100),
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Sample hackathon timeline
|
||||||
|
let hackathon_timeline = vec![
|
||||||
|
(
|
||||||
|
"hackathon-001",
|
||||||
|
HackathonPhase::Registration,
|
||||||
|
"Registration Phase",
|
||||||
|
Some("Register your team and submit initial project ideas".to_string()),
|
||||||
|
"2025-10-01T00:00:00Z",
|
||||||
|
"2025-10-10T23:59:59Z",
|
||||||
|
true,
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"hackathon-001",
|
||||||
|
HackathonPhase::Ideation,
|
||||||
|
"Ideation & Planning",
|
||||||
|
Some("Brainstorm and plan your AI solution".to_string()),
|
||||||
|
"2025-10-11T00:00:00Z",
|
||||||
|
"2025-10-14T23:59:59Z",
|
||||||
|
false,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"hackathon-001",
|
||||||
|
HackathonPhase::Development,
|
||||||
|
"Development Sprint",
|
||||||
|
Some("Build your AI-powered application".to_string()),
|
||||||
|
"2025-10-15T00:00:00Z",
|
||||||
|
"2025-10-16T23:59:59Z",
|
||||||
|
false,
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"hackathon-001",
|
||||||
|
HackathonPhase::Submission,
|
||||||
|
"Project Submission",
|
||||||
|
Some("Submit your final project and demo video".to_string()),
|
||||||
|
"2025-10-17T00:00:00Z",
|
||||||
|
"2025-10-17T12:00:00Z",
|
||||||
|
false,
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"hackathon-001",
|
||||||
|
HackathonPhase::Judging,
|
||||||
|
"Judging & Awards",
|
||||||
|
Some("Presentations and prize ceremony".to_string()),
|
||||||
|
"2025-10-17T13:00:00Z",
|
||||||
|
"2025-10-17T18:00:00Z",
|
||||||
|
false,
|
||||||
|
5,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Sample hackathon submissions
|
||||||
|
let hackathon_submissions = vec![
|
||||||
|
(
|
||||||
|
"hackathon-001",
|
||||||
|
"team-dev-001",
|
||||||
|
"AI-Powered Health Monitor",
|
||||||
|
"A machine learning application that predicts health risks using wearable device data.",
|
||||||
|
Some("https://github.com/team-dev/ai-health-monitor".to_string()),
|
||||||
|
Some("https://demo.ai-health-monitor.com".to_string()),
|
||||||
|
None,
|
||||||
|
vec!["Python".to_string(), "TensorFlow".to_string(), "React".to_string()],
|
||||||
|
SubmissionStatus::Submitted,
|
||||||
|
"2025-10-17T11:30:00Z",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"hackathon-001",
|
||||||
|
"team-design-001",
|
||||||
|
"Smart City Traffic Optimizer",
|
||||||
|
"AI system that optimizes traffic flow using computer vision and predictive analytics.",
|
||||||
|
Some("https://github.com/team-design/smart-traffic".to_string()),
|
||||||
|
Some("https://demo.smart-traffic.com".to_string()),
|
||||||
|
Some("https://slides.smart-traffic.com/presentation".to_string()),
|
||||||
|
vec!["JavaScript".to_string(), "Node.js".to_string(), "OpenCV".to_string()],
|
||||||
|
SubmissionStatus::UnderReview,
|
||||||
|
"2025-10-17T10:45:00Z",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Seed hackathons
|
||||||
|
for (
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
registration_deadline,
|
||||||
|
max_participants,
|
||||||
|
status,
|
||||||
|
theme,
|
||||||
|
rules,
|
||||||
|
prizes,
|
||||||
|
organizers,
|
||||||
|
) in hackathons {
|
||||||
|
db.query("DELETE type::thing('app_hackathons', $id)")
|
||||||
|
.bind(("id", id))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let hackathon = HackathonSchema {
|
||||||
|
id: Thing::from(("app_hackathons", id)),
|
||||||
|
name: name.into(),
|
||||||
|
description: description.into(),
|
||||||
|
start_date: DateTime::parse_from_rfc3339(start_date)?.with_timezone(&Utc),
|
||||||
|
end_date: DateTime::parse_from_rfc3339(end_date)?.with_timezone(&Utc),
|
||||||
|
registration_deadline: DateTime::parse_from_rfc3339(registration_deadline)?.with_timezone(&Utc),
|
||||||
|
max_participants,
|
||||||
|
status,
|
||||||
|
theme,
|
||||||
|
rules,
|
||||||
|
prizes,
|
||||||
|
previous_winners: None,
|
||||||
|
organizers,
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: Some(get_iso_date()),
|
||||||
|
updated_at: Some(get_iso_date()),
|
||||||
|
};
|
||||||
|
|
||||||
|
db.create::<Option<HackathonSchema>>(("app_hackathons", id))
|
||||||
|
.content(hackathon)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("✅ Inserted hackathon: {name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed hackathon events
|
||||||
|
for (
|
||||||
|
hackathon_id,
|
||||||
|
event_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
event_type,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
location,
|
||||||
|
virtual_link,
|
||||||
|
max_attendees,
|
||||||
|
is_mandatory,
|
||||||
|
) in hackathon_events {
|
||||||
|
db.query("DELETE type::thing('app_hackathon_events', $id)")
|
||||||
|
.bind(("id", event_id))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let event = HackathonEventsSchema {
|
||||||
|
id: Thing::from(("app_hackathon_events", event_id)),
|
||||||
|
hackathon_id: Thing::from(("app_hackathons", hackathon_id)),
|
||||||
|
title: title.into(),
|
||||||
|
description,
|
||||||
|
event_type,
|
||||||
|
start_time: DateTime::parse_from_rfc3339(start_time)?.with_timezone(&Utc),
|
||||||
|
end_time: DateTime::parse_from_rfc3339(end_time)?.with_timezone(&Utc),
|
||||||
|
location,
|
||||||
|
virtual_link,
|
||||||
|
max_attendees,
|
||||||
|
is_mandatory,
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: Some(get_iso_date()),
|
||||||
|
updated_at: Some(get_iso_date()),
|
||||||
|
};
|
||||||
|
|
||||||
|
db.create::<Option<HackathonEventsSchema>>(("app_hackathon_events", event_id))
|
||||||
|
.content(event)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("✅ Inserted hackathon event: {title}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed hackathon timeline
|
||||||
|
for (
|
||||||
|
hackathon_id,
|
||||||
|
phase,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
is_active,
|
||||||
|
order,
|
||||||
|
) in hackathon_timeline {
|
||||||
|
let timeline_id = format!("timeline-{}-{}", hackathon_id, order);
|
||||||
|
|
||||||
|
db.query("DELETE type::thing('app_hackathon_timeline', $id)")
|
||||||
|
.bind(("id", timeline_id.clone()))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let timeline = HackathonTimelineSchema {
|
||||||
|
id: Thing::from(("app_hackathon_timeline", timeline_id.as_str())),
|
||||||
|
hackathon_id: Thing::from(("app_hackathons", hackathon_id)),
|
||||||
|
phase,
|
||||||
|
title: title.into(),
|
||||||
|
description,
|
||||||
|
start_date: DateTime::parse_from_rfc3339(start_date)?.with_timezone(&Utc),
|
||||||
|
end_date: DateTime::parse_from_rfc3339(end_date)?.with_timezone(&Utc),
|
||||||
|
is_active,
|
||||||
|
order,
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: Some(get_iso_date()),
|
||||||
|
updated_at: Some(get_iso_date()),
|
||||||
|
};
|
||||||
|
|
||||||
|
db.create::<Option<HackathonTimelineSchema>>(("app_hackathon_timeline", timeline_id))
|
||||||
|
.content(timeline)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("✅ Inserted hackathon timeline: {title}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed hackathon submissions
|
||||||
|
for (
|
||||||
|
hackathon_id,
|
||||||
|
team_id,
|
||||||
|
project_name,
|
||||||
|
description,
|
||||||
|
repository_url,
|
||||||
|
demo_url,
|
||||||
|
slides_url,
|
||||||
|
technologies,
|
||||||
|
submission_status,
|
||||||
|
submitted_at,
|
||||||
|
) in hackathon_submissions {
|
||||||
|
let submission_id = format!("submission-{}-{}", hackathon_id, team_id);
|
||||||
|
|
||||||
|
db.query("DELETE type::thing('app_hackathon_submissions', $id)")
|
||||||
|
.bind(("id", submission_id.clone()))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let submission = HackathonSubmissionsSchema {
|
||||||
|
id: Thing::from(("app_hackathon_submissions", submission_id.as_str())),
|
||||||
|
hackathon_id: Thing::from(("app_hackathons", hackathon_id)),
|
||||||
|
judge_feedback: None,
|
||||||
|
team_id: Some(Thing::from(("app_teams", team_id))),
|
||||||
|
project_name: Some(project_name.into()),
|
||||||
|
description: Some(description.into()),
|
||||||
|
repository_url,
|
||||||
|
upload_file_url: None,
|
||||||
|
demo_url,
|
||||||
|
slides_url,
|
||||||
|
technologies: Some(technologies),
|
||||||
|
contact_instagram: None,
|
||||||
|
contact_twitter: None,
|
||||||
|
contact_linkedin: None,
|
||||||
|
contact_facebook: None,
|
||||||
|
contact_youtube: None,
|
||||||
|
contact_tiktok: None,
|
||||||
|
contact_other: None,
|
||||||
|
submission_status: Some(submission_status),
|
||||||
|
submitted_at: Some(DateTime::parse_from_rfc3339(submitted_at)?.with_timezone(&Utc)),
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: Some(get_iso_date()),
|
||||||
|
updated_at: Some(get_iso_date()),
|
||||||
|
};
|
||||||
|
|
||||||
|
db.create::<Option<HackathonSubmissionsSchema>>(("app_hackathon_submissions", submission_id))
|
||||||
|
.content(submission)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("✅ Inserted hackathon submission: {project_name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("✅ All Hackathons seeded");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,77 @@
|
|||||||
|
use imphnen_iam::PermissionsEnum;
|
||||||
|
use imphnen_utils::{get_iso_date};
|
||||||
|
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>> {
|
||||||
|
let env = &imphnen_libs::environment::ENV;
|
||||||
|
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.clone())
|
||||||
|
.use_db(env.surrealdb_dbname.clone())
|
||||||
|
.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,
|
||||||
|
PermissionsEnum::ReadListMentors,
|
||||||
|
PermissionsEnum::ReadDetailMentors,
|
||||||
|
PermissionsEnum::RegisterMentors,
|
||||||
|
PermissionsEnum::ReadOwnMentorProfile,
|
||||||
|
PermissionsEnum::UpdateOwnMentorProfile,
|
||||||
|
PermissionsEnum::ReadOwnMentorStatus,
|
||||||
|
PermissionsEnum::UpdateMentors,
|
||||||
|
PermissionsEnum::VerifyMentors,
|
||||||
|
PermissionsEnum::DeleteMentors,
|
||||||
|
PermissionsEnum::Administrator,
|
||||||
|
] {
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("✅ All Permissions seeded");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
use imphnen_utils::{get_iso_date};
|
||||||
|
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>> {
|
||||||
|
let env = &imphnen_libs::environment::ENV;
|
||||||
|
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.clone())
|
||||||
|
.use_db(env.surrealdb_dbname.clone())
|
||||||
|
.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",
|
||||||
|
Some("2025-02-28T14:53:58.576688+00"),
|
||||||
|
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",
|
||||||
|
Some("2025-02-22T15:38:39.868306+00"),
|
||||||
|
Some("2025-02-22T15:38:39.868306+00"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"3b9f8c4e-6a2d-4f8a-9a12-2d6f8b3c4e5a",
|
||||||
|
"Mentor",
|
||||||
|
Some("2025-07-06T10:00:00.000000+00"),
|
||||||
|
Some("2025-07-06T10:00:00.000000+00"),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (id, name, _created_at, _updated_at) in roles {
|
||||||
|
db.query("DELETE type::thing('app_roles', $id)")
|
||||||
|
.bind(("id", id))
|
||||||
|
.await?;
|
||||||
|
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,115 @@
|
|||||||
|
use imphnen_iam::{get_iso_date, make_thing, PermissionsEnum};
|
||||||
|
use std::error::Error;
|
||||||
|
use surrealdb::engine::any;
|
||||||
|
use surrealdb::opt::auth::Root;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let env = &imphnen_libs::environment::ENV;
|
||||||
|
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.clone())
|
||||||
|
.use_db(env.surrealdb_dbname.clone())
|
||||||
|
.await?;
|
||||||
|
db.query("DEFINE INDEX user_email_index ON TABLE users COLUMNS email UNIQUE;")
|
||||||
|
|
||||||
|
.await?;
|
||||||
|
db.query("DEFINE INDEX role_name_idx ON TABLE roles COLUMNS name UNIQUE;")
|
||||||
|
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("✅ Index 'user_email_index' defined on table 'users' for column 'email'.");
|
||||||
|
|
||||||
|
let roles_permissions = vec![
|
||||||
|
(
|
||||||
|
"f6b03f25-e416-4893-ac88-caaa690afb07",
|
||||||
|
vec![
|
||||||
|
// Only Administrator permission - grants access to everything
|
||||||
|
PermissionsEnum::Administrator,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"3b9f8c4e-6a2d-4f8a-9a12-2d6f8b3c4e5a",
|
||||||
|
vec![
|
||||||
|
PermissionsEnum::ReadListUsers, // Added ReadListUsers permission
|
||||||
|
PermissionsEnum::ReadOwnMentorProfile,
|
||||||
|
PermissionsEnum::UpdateOwnMentorProfile,
|
||||||
|
PermissionsEnum::ReadOwnMentorStatus,
|
||||||
|
PermissionsEnum::ReadListMentors,
|
||||||
|
PermissionsEnum::ReadDetailMentors,
|
||||||
|
PermissionsEnum::ReadListGachaItems,
|
||||||
|
PermissionsEnum::ReadDetailGachaItems,
|
||||||
|
PermissionsEnum::ReadDetailGachaRolls,
|
||||||
|
PermissionsEnum::CreateGachaRolls,
|
||||||
|
PermissionsEnum::ExecuteGachaRolls,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||||
|
vec![
|
||||||
|
PermissionsEnum::ReadListGachaItems,
|
||||||
|
PermissionsEnum::ReadDetailGachaItems,
|
||||||
|
PermissionsEnum::ReadListUsers,
|
||||||
|
PermissionsEnum::ReadDetailUsers,
|
||||||
|
PermissionsEnum::CreateGachaClaims,
|
||||||
|
PermissionsEnum::ReadDetailGachaClaims,
|
||||||
|
PermissionsEnum::ReadDetailGachaRolls,
|
||||||
|
PermissionsEnum::CreateGachaRolls,
|
||||||
|
PermissionsEnum::ExecuteGachaRolls,
|
||||||
|
PermissionsEnum::RegisterMentors,
|
||||||
|
PermissionsEnum::ReadListMentors,
|
||||||
|
PermissionsEnum::ReadDetailMentors,
|
||||||
|
PermissionsEnum::ReadOwnMentorProfile,
|
||||||
|
PermissionsEnum::ReadOwnMentorStatus,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"50133429-f4b1-4249-9f97-7b86e6ee9d86",
|
||||||
|
vec![
|
||||||
|
// Staff should be able to list roles and permissions in tests
|
||||||
|
PermissionsEnum::ReadListRoles,
|
||||||
|
PermissionsEnum::ReadListPermissions,
|
||||||
|
PermissionsEnum::ReadListUsers,
|
||||||
|
PermissionsEnum::ReadListMentors,
|
||||||
|
PermissionsEnum::ReadDetailUsers,
|
||||||
|
PermissionsEnum::ActivateUsers,
|
||||||
|
PermissionsEnum::ReadDetailRoles,
|
||||||
|
PermissionsEnum::ReadDetailPermissions,
|
||||||
|
PermissionsEnum::ReadListGachaItems,
|
||||||
|
PermissionsEnum::ReadDetailGachaItems,
|
||||||
|
PermissionsEnum::ReadListMentors,
|
||||||
|
PermissionsEnum::ReadDetailMentors,
|
||||||
|
PermissionsEnum::ReadDetailGachaRolls,
|
||||||
|
PermissionsEnum::CreateGachaRolls,
|
||||||
|
PermissionsEnum::ExecuteGachaRolls,
|
||||||
|
PermissionsEnum::ManageAllTeams,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"60f1aeb7-dad2-4e06-bcb5-be1ba510c906",
|
||||||
|
vec![PermissionsEnum::ActivateUsers],
|
||||||
|
),
|
||||||
|
("6d4fea5d-4a08-4b8a-9782-f2ab2183dcf0", vec![]),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (role_id, permissions) in roles_permissions {
|
||||||
|
let permission_refs: Vec<_> = permissions
|
||||||
|
.iter()
|
||||||
|
.map(|perm| make_thing("app_permissions", &perm.id()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
db.query("UPDATE type::thing('app_roles', $role_id) SET permissions = $permissions, updated_at = $updated_at WHERE is_deleted = false")
|
||||||
|
.bind(("role_id", role_id))
|
||||||
|
.bind(("permissions", permission_refs))
|
||||||
|
.bind(("updated_at", get_iso_date()))
|
||||||
|
.await?;
|
||||||
|
println!("✅ Permissions updated for role: {role_id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("✅ All roles permissions updated!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
use imphnen_iam::v1::teams::TeamsSchema;
|
||||||
|
use imphnen_utils::get_iso_date;
|
||||||
|
use std::error::Error;
|
||||||
|
use surrealdb::{opt::auth::Root, sql::Thing};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let env = &imphnen_libs::environment::ENV;
|
||||||
|
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.clone())
|
||||||
|
.use_db(env.surrealdb_dbname.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let teams = vec![
|
||||||
|
(
|
||||||
|
"team-dev-001",
|
||||||
|
"Development Team",
|
||||||
|
Some("Core development team for the platform".to_string()),
|
||||||
|
"c3b1d6a8-8d4f-4b36-b789-2e532ec7a7b2", // admin user
|
||||||
|
true,
|
||||||
|
Some(10),
|
||||||
|
Some(vec!["Rust".to_string(), "Backend".to_string()]),
|
||||||
|
Some("Remote".to_string()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"team-design-001",
|
||||||
|
"Design Team",
|
||||||
|
Some("UI/UX design team".to_string()),
|
||||||
|
"c3b1d6a8-8d4f-4b36-b789-2e532ec7a7b2", // admin user
|
||||||
|
true,
|
||||||
|
Some(5),
|
||||||
|
Some(vec!["Figma".to_string(), "Design".to_string()]),
|
||||||
|
Some("Remote".to_string()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"team-qa-001",
|
||||||
|
"Quality Assurance Team",
|
||||||
|
Some("Testing and quality assurance team".to_string()),
|
||||||
|
"c3b1d6a8-8d4f-4b36-b789-2e532ec7a7b2", // admin user
|
||||||
|
false,
|
||||||
|
Some(8),
|
||||||
|
Some(vec!["Testing".to_string(), "Automation".to_string()]),
|
||||||
|
Some("Remote".to_string()),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (id, name, description, leader_id, is_open, max_members, skills_required, location) in teams {
|
||||||
|
db.query("DELETE type::thing('app_teams', $id)")
|
||||||
|
.bind(("id", id))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let team = TeamsSchema {
|
||||||
|
id: Thing::from(("app_teams", id)),
|
||||||
|
name: name.into(),
|
||||||
|
description,
|
||||||
|
leader_id: Thing::from(("app_users", leader_id)),
|
||||||
|
is_open,
|
||||||
|
max_members,
|
||||||
|
skills_required,
|
||||||
|
location,
|
||||||
|
avatar: None,
|
||||||
|
website_url: None,
|
||||||
|
github_url: None,
|
||||||
|
is_active: true,
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: get_iso_date(),
|
||||||
|
updated_at: get_iso_date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
db.create::<Option<TeamsSchema>>(("app_teams", id))
|
||||||
|
.content(team)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("✅ Inserted team: {name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("✅ All Teams seeded");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
use imphnen_cms::v1::landing::events::events_schema::EventsSchema;
|
||||||
|
use imphnen_cms::v1::landing::testimonials::testimonials_schema::TestimonialsSchema;
|
||||||
|
use imphnen_dimentorin::v1::mentors::mentors_schema::MentorSchema;
|
||||||
|
use imphnen_dimentorin::v1::mentors::mentors_dto::MentoringRate;
|
||||||
|
use imphnen_hackathon::v1::hackathon::hackathon_schema::{
|
||||||
|
HackathonSchema, HackathonEventsSchema, HackathonTimelineSchema,
|
||||||
|
HackathonStatus, HackathonEventType, HackathonPhase
|
||||||
|
};
|
||||||
|
use imphnen_utils::get_iso_date;
|
||||||
|
use std::error::Error;
|
||||||
|
use surrealdb::{opt::auth::Root, sql::Thing};
|
||||||
|
use chrono::Utc;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let env = &imphnen_libs::environment::ENV;
|
||||||
|
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.clone())
|
||||||
|
.use_db(env.surrealdb_dbname.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Seed Events - handle existing data
|
||||||
|
let event = EventsSchema {
|
||||||
|
id: Thing::from(("app_events", "1")),
|
||||||
|
name: "Test Event".to_string(),
|
||||||
|
description: "Test event description".to_string(),
|
||||||
|
detail_link: "https://example.com/event".to_string(),
|
||||||
|
price: 50.0,
|
||||||
|
is_online: true,
|
||||||
|
start_date: get_iso_date(),
|
||||||
|
end_date: get_iso_date(),
|
||||||
|
location: None,
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: get_iso_date(),
|
||||||
|
updated_at: get_iso_date(),
|
||||||
|
};
|
||||||
|
match db.create::<Option<EventsSchema>>(("app_events", "1"))
|
||||||
|
.content(event)
|
||||||
|
.await {
|
||||||
|
Ok(_) => println!("✅ Inserted test event"),
|
||||||
|
Err(_) => println!("⚠️ Test event already exists, skipping"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Seed Testimonials - handle existing data
|
||||||
|
let testimonial = TestimonialsSchema {
|
||||||
|
id: Thing::from(("app_testimonials", "1")),
|
||||||
|
user: Thing::from(("app_users", "c3b1d6a8-8d4f-4b36-b789-2e532ec7a7b2")),
|
||||||
|
role: "Student".to_string(),
|
||||||
|
content: "This is a great platform!".to_string(),
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: get_iso_date(),
|
||||||
|
updated_at: get_iso_date(),
|
||||||
|
};
|
||||||
|
match db.create::<Option<TestimonialsSchema>>(("app_testimonials", "1"))
|
||||||
|
.content(testimonial)
|
||||||
|
.await {
|
||||||
|
Ok(_) => println!("✅ Inserted test testimonial"),
|
||||||
|
Err(_) => println!("⚠️ Test testimonial already exists, skipping"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Seed Hackathon - handle existing data
|
||||||
|
let hackathon = HackathonSchema {
|
||||||
|
id: Thing::from(("app_hackathons", "1")),
|
||||||
|
name: "Test Hackathon".to_string(),
|
||||||
|
description: "Test hackathon description".to_string(),
|
||||||
|
start_date: Utc::now() + chrono::Duration::days(30),
|
||||||
|
end_date: Utc::now() + chrono::Duration::days(37),
|
||||||
|
registration_deadline: Utc::now() + chrono::Duration::days(25),
|
||||||
|
max_participants: Some(100),
|
||||||
|
status: HackathonStatus::Draft,
|
||||||
|
theme: Some("Technology".to_string()),
|
||||||
|
rules: Some("Follow the rules".to_string()),
|
||||||
|
prizes: Some(vec![]),
|
||||||
|
previous_winners: Some(vec![]),
|
||||||
|
organizers: vec!["c3b1d6a8-8d4f-4b36-b789-2e532ec7a7b2".to_string()],
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: Some(get_iso_date()),
|
||||||
|
updated_at: Some(get_iso_date()),
|
||||||
|
};
|
||||||
|
match db.create::<Option<HackathonSchema>>(("app_hackathons", "1"))
|
||||||
|
.content(hackathon)
|
||||||
|
.await {
|
||||||
|
Ok(_) => println!("✅ Inserted test hackathon"),
|
||||||
|
Err(_) => println!("⚠️ Test hackathon already exists, skipping"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Seed Hackathon Event
|
||||||
|
let hackathon_event = HackathonEventsSchema {
|
||||||
|
id: Thing::from(("app_hackathon_events", "test-event-001")),
|
||||||
|
hackathon_id: Thing::from(("app_hackathons", "1")),
|
||||||
|
title: "Test Event".to_string(),
|
||||||
|
description: Some("Test hackathon event description".to_string()),
|
||||||
|
event_type: HackathonEventType::Workshop,
|
||||||
|
start_time: Utc::now() + chrono::Duration::days(30),
|
||||||
|
end_time: Utc::now() + chrono::Duration::days(30) + chrono::Duration::hours(6),
|
||||||
|
location: Some("Online".to_string()),
|
||||||
|
virtual_link: None,
|
||||||
|
max_attendees: Some(50),
|
||||||
|
is_mandatory: false,
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: Some(get_iso_date()),
|
||||||
|
updated_at: Some(get_iso_date()),
|
||||||
|
};
|
||||||
|
// Try to create hackathon event, skip if already exists
|
||||||
|
match db.create::<Option<HackathonEventsSchema>>(("app_hackathon_events", "test-event-001"))
|
||||||
|
.content(hackathon_event)
|
||||||
|
.await {
|
||||||
|
Ok(_) => println!("✅ Inserted test hackathon event"),
|
||||||
|
Err(_) => println!("⚠️ Test hackathon event already exists, skipping"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Seed Hackathon Timeline
|
||||||
|
let hackathon_timeline = HackathonTimelineSchema {
|
||||||
|
id: Thing::from(("app_hackathon_timeline", "test-timeline-001")),
|
||||||
|
hackathon_id: Thing::from(("app_hackathons", "1")),
|
||||||
|
phase: HackathonPhase::Registration,
|
||||||
|
title: "Test Timeline".to_string(),
|
||||||
|
description: Some("Test hackathon timeline description".to_string()),
|
||||||
|
start_date: Utc::now(),
|
||||||
|
end_date: Utc::now() + chrono::Duration::days(7),
|
||||||
|
is_active: true,
|
||||||
|
order: 1,
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: Some(get_iso_date()),
|
||||||
|
updated_at: Some(get_iso_date()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to create hackathon timeline, skip if already exists
|
||||||
|
match db.create::<Option<HackathonTimelineSchema>>(("app_hackathon_timeline", "test-timeline-001"))
|
||||||
|
.content(hackathon_timeline)
|
||||||
|
.await {
|
||||||
|
Ok(_) => println!("✅ Inserted test hackathon timeline"),
|
||||||
|
Err(_) => println!("⚠️ Test hackathon timeline already exists, skipping"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Seed Mentor - handle existing data
|
||||||
|
let mentor = MentorSchema {
|
||||||
|
id: Thing::from(("app_mentors", "e6f78d23-83bf-5c2b-bcd4-001345678901")),
|
||||||
|
user_id: Some(Thing::from(("app_users", "e6f78d23-83bf-5c2b-bcd4-001345678901"))),
|
||||||
|
industries: vec!["Technology".to_string(), "Education".to_string()],
|
||||||
|
expertise: vec!["Software Development".to_string()],
|
||||||
|
languages: vec!["English".to_string(), "Indonesian".to_string()],
|
||||||
|
current_company: "Tech Corp".to_string(),
|
||||||
|
current_role: "Senior Engineer".to_string(),
|
||||||
|
years_of_experience: 5,
|
||||||
|
topics_of_interest: vec!["Rust".to_string(), "Web Development".to_string()],
|
||||||
|
preferred_mentee_level: vec!["Beginner".to_string()],
|
||||||
|
preferred_mentoring_formats: vec!["1:1".to_string(), "Group".to_string()],
|
||||||
|
availability_commitment: "Weekly".to_string(),
|
||||||
|
mentoring_rate: MentoringRate {
|
||||||
|
amount: 100,
|
||||||
|
currency: "IDR".to_string(),
|
||||||
|
per_duration: "hour".to_string(),
|
||||||
|
},
|
||||||
|
status: "active".to_string(),
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: get_iso_date(),
|
||||||
|
updated_at: get_iso_date(),
|
||||||
|
};
|
||||||
|
match db.create::<Option<MentorSchema>>(("app_mentors", "e6f78d23-83bf-5c2b-bcd4-001345678901"))
|
||||||
|
.content(mentor)
|
||||||
|
.await {
|
||||||
|
Ok(_) => println!("✅ Inserted test mentor"),
|
||||||
|
Err(_) => println!("⚠️ Test mentor already exists, skipping"),
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("✅ All test data seeded successfully");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,433 @@
|
|||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use imphnen_hackathon::v1::hackathon::hackathon_schema::{
|
||||||
|
HackathonSchema, HackathonTimelineSchema, HackathonSubmissionsSchema,
|
||||||
|
HackathonStatus, HackathonPhase, SubmissionStatus, Prize,
|
||||||
|
};
|
||||||
|
use imphnen_iam::{UsersSchema, v1::teams::TeamsSchema};
|
||||||
|
use imphnen_utils::{get_iso_date, hash_password};
|
||||||
|
use std::error::Error;
|
||||||
|
use surrealdb::{opt::auth::Root, sql::Thing};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let env = &imphnen_libs::environment::ENV;
|
||||||
|
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.clone())
|
||||||
|
.use_db(env.surrealdb_dbname.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Test users for submission testing
|
||||||
|
let test_users = vec![
|
||||||
|
(
|
||||||
|
"test-user-001",
|
||||||
|
"testuser1@example.com",
|
||||||
|
"Test User 1",
|
||||||
|
"5713cb37-dc02-4e87-8048-d7a41d352059", // User role
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"test-user-002",
|
||||||
|
"testuser2@example.com",
|
||||||
|
"Test User 2",
|
||||||
|
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"test-user-003",
|
||||||
|
"testuser3@example.com",
|
||||||
|
"Test User 3",
|
||||||
|
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Test teams for submission testing
|
||||||
|
let test_teams = vec![
|
||||||
|
(
|
||||||
|
"test-team-001",
|
||||||
|
"Test Team Alpha",
|
||||||
|
Some("Team for testing hackathon submissions".to_string()),
|
||||||
|
"test-user-001", // team leader
|
||||||
|
true,
|
||||||
|
Some(5),
|
||||||
|
Some(vec!["JavaScript".to_string(), "React".to_string()]),
|
||||||
|
Some("Remote".to_string()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"test-team-002",
|
||||||
|
"Test Team Beta",
|
||||||
|
Some("Another team for testing submissions".to_string()),
|
||||||
|
"test-user-002",
|
||||||
|
true,
|
||||||
|
Some(4),
|
||||||
|
Some(vec!["Python".to_string(), "Django".to_string()]),
|
||||||
|
Some("Remote".to_string()),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Test hackathon for submission testing
|
||||||
|
let test_hackathons = vec![
|
||||||
|
(
|
||||||
|
"test-hackathon-001",
|
||||||
|
"Test Hackathon 2025",
|
||||||
|
"Hackathon for testing submission functionality.",
|
||||||
|
"2025-12-01T09:00:00Z",
|
||||||
|
"2025-12-03T18:00:00Z",
|
||||||
|
"2025-11-25T23:59:59Z",
|
||||||
|
Some(50),
|
||||||
|
HackathonStatus::RegistrationOpen,
|
||||||
|
Some("Testing & Development".to_string()),
|
||||||
|
Some("1. Test all submission features\n2. Teams can have 2-5 members\n3. Submit by deadline".to_string()),
|
||||||
|
Some(vec![
|
||||||
|
Prize { position: 1, title: "Test Winner".to_string(), description: Some("Best test submission".to_string()), value: Some("$1,000".to_string()) },
|
||||||
|
Prize { position: 2, title: "Test Runner-up".to_string(), description: Some("Second best submission".to_string()), value: Some("$500".to_string()) },
|
||||||
|
]),
|
||||||
|
vec!["c3b1d6a8-8d4f-4b36-b789-2e532ec7a7b2".to_string()], // admin user
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Test hackathon timeline
|
||||||
|
let test_timeline = vec![
|
||||||
|
(
|
||||||
|
"test-hackathon-001",
|
||||||
|
HackathonPhase::Registration,
|
||||||
|
"Registration Phase",
|
||||||
|
Some("Register your team for the test hackathon".to_string()),
|
||||||
|
"2025-11-20T00:00:00Z",
|
||||||
|
"2025-11-25T23:59:59Z",
|
||||||
|
true,
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"test-hackathon-001",
|
||||||
|
HackathonPhase::Development,
|
||||||
|
"Development Phase",
|
||||||
|
Some("Build your test project".to_string()),
|
||||||
|
"2025-12-01T00:00:00Z",
|
||||||
|
"2025-12-02T23:59:59Z",
|
||||||
|
false,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"test-hackathon-001",
|
||||||
|
HackathonPhase::Submission,
|
||||||
|
"Submission Phase",
|
||||||
|
Some("Submit your test project".to_string()),
|
||||||
|
"2025-12-03T00:00:00Z",
|
||||||
|
"2025-12-03T12:00:00Z",
|
||||||
|
false,
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Test submissions
|
||||||
|
let test_submissions = vec![
|
||||||
|
(
|
||||||
|
"test-hackathon-001",
|
||||||
|
"test-team-001",
|
||||||
|
"Test Project Alpha",
|
||||||
|
"A comprehensive test project demonstrating all features.",
|
||||||
|
Some("https://github.com/test-team-alpha/test-project".to_string()),
|
||||||
|
Some("https://demo.test-project-alpha.com".to_string()),
|
||||||
|
Some("https://slides.test-project-alpha.com".to_string()),
|
||||||
|
vec!["JavaScript".to_string(), "React".to_string(), "Node.js".to_string()],
|
||||||
|
SubmissionStatus::Draft,
|
||||||
|
"2025-12-02T10:00:00Z",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"test-hackathon-001",
|
||||||
|
"test-team-002",
|
||||||
|
"Test Project Beta",
|
||||||
|
"Another test project with different technologies.",
|
||||||
|
Some("https://github.com/test-team-beta/test-project".to_string()),
|
||||||
|
Some("https://demo.test-project-beta.com".to_string()),
|
||||||
|
None,
|
||||||
|
vec!["Python".to_string(), "Django".to_string(), "PostgreSQL".to_string()],
|
||||||
|
SubmissionStatus::Submitted,
|
||||||
|
"2025-12-03T09:30:00Z",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Seed test users
|
||||||
|
for (id, email, fullname, role_id) in test_users {
|
||||||
|
db.query("DELETE type::thing('app_users', $id)")
|
||||||
|
.bind(("id", id))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let user = UsersSchema {
|
||||||
|
id: Thing::from(("app_users", id)),
|
||||||
|
fullname: fullname.into(),
|
||||||
|
legal_name: Some(format!("{} Legal Name", fullname)),
|
||||||
|
email: email.into(),
|
||||||
|
password: hash_password("password").unwrap(),
|
||||||
|
avatar: Some("https://example.com/avatar.jpg".into()),
|
||||||
|
phone_number: "081234567890".into(),
|
||||||
|
phone_for_verification: Some("081234567890".into()),
|
||||||
|
is_active: true,
|
||||||
|
is_deleted: false,
|
||||||
|
mentor_id: None,
|
||||||
|
gender: Some("male".into()),
|
||||||
|
birthdate: Some("1995-01-01".into()),
|
||||||
|
domicile: Some("Jakarta, Indonesia".into()),
|
||||||
|
bio: Some(format!("{} is a test user for hackathon submissions.", fullname)),
|
||||||
|
last_education: Some("S1 Computer Science".into()),
|
||||||
|
linkedin_url: Some("https://linkedin.com/in/testuser".into()),
|
||||||
|
github_url: Some("https://github.com/testuser".into()),
|
||||||
|
cv_url: Some("https://example.com/cv.pdf".into()),
|
||||||
|
portfolio_url: Some("https://example.com/portfolio".into()),
|
||||||
|
website_url: Some("https://example.com/website".into()),
|
||||||
|
twitter_url: Some("https://twitter.com/testuser".into()),
|
||||||
|
location: Some("Jakarta, Indonesia".into()),
|
||||||
|
skills: Some(vec!["JavaScript".to_string(), "Python".to_string()]),
|
||||||
|
experience: None,
|
||||||
|
education: None,
|
||||||
|
career_status: Some("Developer".into()),
|
||||||
|
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 test user: {fullname} ({email})");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed test teams
|
||||||
|
for (id, name, description, leader_id, is_open, max_members, skills_required, location) in test_teams {
|
||||||
|
db.query("DELETE type::thing('app_teams', $id)")
|
||||||
|
.bind(("id", id))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let team = TeamsSchema {
|
||||||
|
id: Thing::from(("app_teams", id)),
|
||||||
|
name: name.into(),
|
||||||
|
description,
|
||||||
|
leader_id: Thing::from(("app_users", leader_id)),
|
||||||
|
is_open,
|
||||||
|
max_members,
|
||||||
|
skills_required,
|
||||||
|
location,
|
||||||
|
avatar: None,
|
||||||
|
website_url: None,
|
||||||
|
github_url: None,
|
||||||
|
is_active: true,
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: get_iso_date(),
|
||||||
|
updated_at: get_iso_date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
db.create::<Option<TeamsSchema>>(("app_teams", id))
|
||||||
|
.content(team)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("✅ Inserted test team: {name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed test hackathons
|
||||||
|
for (
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
registration_deadline,
|
||||||
|
max_participants,
|
||||||
|
status,
|
||||||
|
theme,
|
||||||
|
rules,
|
||||||
|
prizes,
|
||||||
|
organizers,
|
||||||
|
) in test_hackathons {
|
||||||
|
db.query("DELETE type::thing('app_hackathons', $id)")
|
||||||
|
.bind(("id", id))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let hackathon = HackathonSchema {
|
||||||
|
id: Thing::from(("app_hackathons", id)),
|
||||||
|
name: name.into(),
|
||||||
|
description: description.into(),
|
||||||
|
start_date: DateTime::parse_from_rfc3339(start_date)?.with_timezone(&Utc),
|
||||||
|
end_date: DateTime::parse_from_rfc3339(end_date)?.with_timezone(&Utc),
|
||||||
|
registration_deadline: DateTime::parse_from_rfc3339(registration_deadline)?.with_timezone(&Utc),
|
||||||
|
max_participants,
|
||||||
|
status: status.clone(),
|
||||||
|
theme: theme.clone(),
|
||||||
|
rules: rules.clone(),
|
||||||
|
prizes: prizes.clone(),
|
||||||
|
previous_winners: None,
|
||||||
|
organizers: organizers.clone(),
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: Some(get_iso_date()),
|
||||||
|
updated_at: Some(get_iso_date()),
|
||||||
|
};
|
||||||
|
|
||||||
|
db.create::<Option<HackathonSchema>>(("app_hackathons", id))
|
||||||
|
.content(hackathon)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("✅ Inserted test hackathon: {name}");
|
||||||
|
// Also create an alias canonical id 'test-hackathon' so tests referencing
|
||||||
|
// /v1/hackathons/test-hackathon/... can find a hackathon record.
|
||||||
|
if id != "test-hackathon" && id.starts_with("test-hackathon") {
|
||||||
|
let alias_id = "test-hackathon";
|
||||||
|
db.query("DELETE type::thing('app_hackathons', $id)")
|
||||||
|
.bind(("id", alias_id))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let alias_hackathon = HackathonSchema {
|
||||||
|
id: Thing::from(("app_hackathons", alias_id)),
|
||||||
|
name: name.into(),
|
||||||
|
description: description.into(),
|
||||||
|
start_date: DateTime::parse_from_rfc3339(start_date)?.with_timezone(&Utc),
|
||||||
|
end_date: DateTime::parse_from_rfc3339(end_date)?.with_timezone(&Utc),
|
||||||
|
registration_deadline: DateTime::parse_from_rfc3339(registration_deadline)?.with_timezone(&Utc),
|
||||||
|
max_participants,
|
||||||
|
status: status.clone(),
|
||||||
|
theme: theme.clone(),
|
||||||
|
rules: rules.clone(),
|
||||||
|
prizes: prizes.clone(),
|
||||||
|
previous_winners: None,
|
||||||
|
organizers: organizers.clone(),
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: Some(get_iso_date()),
|
||||||
|
updated_at: Some(get_iso_date()),
|
||||||
|
};
|
||||||
|
|
||||||
|
db.create::<Option<HackathonSchema>>(("app_hackathons", alias_id))
|
||||||
|
.content(alias_hackathon)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("✅ Inserted test hackathon alias: {alias_id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed test hackathon timeline
|
||||||
|
for (
|
||||||
|
hackathon_id,
|
||||||
|
phase,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
is_active,
|
||||||
|
order,
|
||||||
|
) in test_timeline {
|
||||||
|
let timeline_id = format!("test-timeline-{}-{}", hackathon_id, order);
|
||||||
|
|
||||||
|
db.query("DELETE type::thing('app_hackathon_timeline', $id)")
|
||||||
|
.bind(("id", timeline_id.clone()))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let timeline = HackathonTimelineSchema {
|
||||||
|
id: Thing::from(("app_hackathon_timeline", timeline_id.as_str())),
|
||||||
|
hackathon_id: Thing::from(("app_hackathons", hackathon_id)),
|
||||||
|
phase: phase.clone(),
|
||||||
|
title: title.into(),
|
||||||
|
description: description.clone(),
|
||||||
|
start_date: DateTime::parse_from_rfc3339(start_date)?.with_timezone(&Utc),
|
||||||
|
end_date: DateTime::parse_from_rfc3339(end_date)?.with_timezone(&Utc),
|
||||||
|
is_active,
|
||||||
|
order,
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: Some(get_iso_date()),
|
||||||
|
updated_at: Some(get_iso_date()),
|
||||||
|
};
|
||||||
|
|
||||||
|
db.create::<Option<HackathonTimelineSchema>>(("app_hackathon_timeline", timeline_id))
|
||||||
|
.content(timeline)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("✅ Inserted test hackathon timeline: {title}");
|
||||||
|
|
||||||
|
// Also create alias timeline entries for the canonical test id 'test-hackathon'
|
||||||
|
if hackathon_id != "test-hackathon" && hackathon_id.starts_with("test-hackathon") {
|
||||||
|
let alias_hackathon_id = "test-hackathon";
|
||||||
|
let alias_timeline_id = format!("test-timeline-{}-{}", alias_hackathon_id, order);
|
||||||
|
|
||||||
|
db.query("DELETE type::thing('app_hackathon_timeline', $id)")
|
||||||
|
.bind(("id", alias_timeline_id.clone()))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let alias_timeline = HackathonTimelineSchema {
|
||||||
|
id: Thing::from(("app_hackathon_timeline", alias_timeline_id.as_str())),
|
||||||
|
hackathon_id: Thing::from(("app_hackathons", alias_hackathon_id)),
|
||||||
|
phase: phase.clone(),
|
||||||
|
title: title.into(),
|
||||||
|
description: description.clone(),
|
||||||
|
start_date: DateTime::parse_from_rfc3339(start_date)?.with_timezone(&Utc),
|
||||||
|
end_date: DateTime::parse_from_rfc3339(end_date)?.with_timezone(&Utc),
|
||||||
|
is_active,
|
||||||
|
order,
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: Some(get_iso_date()),
|
||||||
|
updated_at: Some(get_iso_date()),
|
||||||
|
};
|
||||||
|
|
||||||
|
db.create::<Option<HackathonTimelineSchema>>( ("app_hackathon_timeline", alias_timeline_id.clone()) )
|
||||||
|
.content(alias_timeline)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("✅ Inserted test hackathon timeline alias: {alias_timeline_id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed test hackathon submissions
|
||||||
|
for (
|
||||||
|
hackathon_id,
|
||||||
|
team_id,
|
||||||
|
project_name,
|
||||||
|
description,
|
||||||
|
repository_url,
|
||||||
|
demo_url,
|
||||||
|
slides_url,
|
||||||
|
technologies,
|
||||||
|
submission_status,
|
||||||
|
submitted_at,
|
||||||
|
) in test_submissions {
|
||||||
|
let submission_id = format!("test-submission-{}-{}", hackathon_id, team_id);
|
||||||
|
|
||||||
|
db.query("DELETE type::thing('app_hackathon_submissions', $id)")
|
||||||
|
.bind(("id", submission_id.clone()))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let submission = HackathonSubmissionsSchema {
|
||||||
|
id: Thing::from(("app_hackathon_submissions", submission_id.as_str())),
|
||||||
|
hackathon_id: Thing::from(("app_hackathons", hackathon_id)),
|
||||||
|
team_id: Some(Thing::from(("app_teams", team_id))),
|
||||||
|
project_name: Some(project_name.into()),
|
||||||
|
description: Some(description.into()),
|
||||||
|
repository_url,
|
||||||
|
upload_file_url: None,
|
||||||
|
demo_url,
|
||||||
|
slides_url,
|
||||||
|
technologies: Some(technologies),
|
||||||
|
contact_instagram: None,
|
||||||
|
contact_twitter: None,
|
||||||
|
contact_linkedin: None,
|
||||||
|
contact_facebook: None,
|
||||||
|
contact_youtube: None,
|
||||||
|
contact_tiktok: None,
|
||||||
|
contact_other: None,
|
||||||
|
submission_status: Some(submission_status),
|
||||||
|
judge_feedback: None,
|
||||||
|
submitted_at: Some(DateTime::parse_from_rfc3339(submitted_at)?.with_timezone(&Utc)),
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: Some(get_iso_date()),
|
||||||
|
updated_at: Some(get_iso_date()),
|
||||||
|
};
|
||||||
|
|
||||||
|
db.create::<Option<HackathonSubmissionsSchema>>(("app_hackathon_submissions", submission_id))
|
||||||
|
.content(submission)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("✅ Inserted test hackathon submission: {project_name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("✅ All test submission data seeded");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
use imphnen_iam::UsersSchema;
|
||||||
|
use imphnen_utils::{get_iso_date, hash_password};
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use surrealdb::{opt::auth::Root, sql::Thing};
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let env = &imphnen_libs::environment::ENV;
|
||||||
|
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.clone())
|
||||||
|
.use_db(env.surrealdb_dbname.clone())
|
||||||
|
.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",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"testuser1-id",
|
||||||
|
"testuser1@example.com",
|
||||||
|
"Test User 1",
|
||||||
|
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"testuser2-id",
|
||||||
|
"testuser2@example.com",
|
||||||
|
"Test User 2",
|
||||||
|
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"testuser3-id",
|
||||||
|
"testuser3@example.com",
|
||||||
|
"Test User 3",
|
||||||
|
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (id, email, fullname, role_id) in users {
|
||||||
|
db.query("DELETE type::thing('app_users', $id)")
|
||||||
|
.bind(("id", id))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let user = UsersSchema {
|
||||||
|
id: Thing::from(("app_users", id)),
|
||||||
|
fullname: fullname.into(),
|
||||||
|
legal_name: Some(format!("{} Legal Name", fullname)),
|
||||||
|
email: email.into(),
|
||||||
|
password: hash_password("password").unwrap(),
|
||||||
|
avatar: Some("https://example.com/avatar.jpg".into()),
|
||||||
|
phone_number: "081234567890".into(),
|
||||||
|
phone_for_verification: Some("081234567890".into()),
|
||||||
|
is_active: true,
|
||||||
|
is_deleted: false,
|
||||||
|
mentor_id: None,
|
||||||
|
gender: Some("male".into()),
|
||||||
|
birthdate: Some("1990-05-15".into()),
|
||||||
|
domicile: Some("Jakarta, Indonesia".into()),
|
||||||
|
// identity_document_url: None, // Sudah tidak dipakai, bisa dihapus dari schema jika tidak diperlukan
|
||||||
|
bio: Some(format!("{} adalah user dengan data pribadi lengkap untuk testing.", fullname)),
|
||||||
|
last_education: Some("S1 Teknik Informatika".into()),
|
||||||
|
linkedin_url: Some("https://linkedin.com/in/user".into()),
|
||||||
|
github_url: Some("https://github.com/user".into()),
|
||||||
|
cv_url: Some("https://example.com/cv.pdf".into()),
|
||||||
|
portfolio_url: Some("https://example.com/portfolio".into()),
|
||||||
|
website_url: Some("https://example.com/website".into()),
|
||||||
|
twitter_url: Some("https://twitter.com/user".into()),
|
||||||
|
location: Some("Jakarta, Indonesia".into()),
|
||||||
|
skills: Some(vec!["JavaScript".into(), "React".into(), "Node.js".into()]),
|
||||||
|
experience: None,
|
||||||
|
education: None,
|
||||||
|
career_status: Some("Senior Developer".into()),
|
||||||
|
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(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
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")?;
|
||||||
|
run_seed("seed_hackathons")?;
|
||||||
|
run_seed("seed_gacha_rolls")?;
|
||||||
|
run_seed("seed_mentor_user")?;
|
||||||
|
run_seed("seed_test_data")?;
|
||||||
|
println!("\n✅ All seeding completed successfully.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
use imphnen_gateway::gateway_service;
|
||||||
|
use imphnen_libs::axum_init;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
axum_init(|surrealdb_ws, surrealdb_mem| async {
|
||||||
|
gateway_service(surrealdb_ws, surrealdb_mem).await
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
[package]
|
||||||
|
name = "imphnen-cms"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
imphnen-iam.workspace = true
|
||||||
|
imphnen-libs.workspace = true
|
||||||
|
imphnen-utils.workspace = true
|
||||||
|
imphnen-entities.workspace = true
|
||||||
|
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
|
||||||
|
log.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
|
||||||
|
[package.metadata.validator.regex]
|
||||||
|
VALID_URL_REGEX = "^https?://"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
pub mod v1;
|
||||||
|
|
||||||
|
pub use v1::landing;
|
||||||
|
pub use v1::landing::events;
|
||||||
|
pub use v1::landing::testimonials;
|
||||||
|
pub use v1::landing::events::events_public_routes;
|
||||||
|
pub use v1::landing::events::events_protected_routes;
|
||||||
|
pub use v1::landing::testimonials::testimonials_public_routes;
|
||||||
|
pub use v1::landing::testimonials::testimonials_protected_routes;
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
use super::{
|
||||||
|
events_dto::{
|
||||||
|
EventsCreateRequestDto, EventsDetailItemDto, EventsListItemDto,
|
||||||
|
EventsUpdateRequestDto,
|
||||||
|
},
|
||||||
|
events_service::EventsService,
|
||||||
|
};
|
||||||
|
use axum::extract::{Path, Query};
|
||||||
|
use axum::response::IntoResponse;
|
||||||
|
use axum::{Extension, http::HeaderMap};
|
||||||
|
use imphnen_libs::{
|
||||||
|
AppState, MessageResponseDto, MetaRequestDto, ResponseListSuccessDto,
|
||||||
|
ResponseSuccessDto, ValidatedJson,
|
||||||
|
};
|
||||||
|
use imphnen_iam::{PermissionsEnum, require_permissions};
|
||||||
|
|
||||||
|
#[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 = "[PUBLIC] 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 = "[PUBLIC] 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 = "[ADMIN] Create new event", body = MessageResponseDto)
|
||||||
|
),
|
||||||
|
tag = "Events"
|
||||||
|
)]
|
||||||
|
pub async fn post_create_event(
|
||||||
|
headers: HeaderMap,
|
||||||
|
Extension(state): Extension<AppState>,
|
||||||
|
ValidatedJson(payload): ValidatedJson<EventsCreateRequestDto>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
require_permissions!(headers, state, [PermissionsEnum::Administrator], {
|
||||||
|
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 = "[ADMIN] Update event", body = MessageResponseDto)
|
||||||
|
),
|
||||||
|
tag = "Events"
|
||||||
|
)]
|
||||||
|
pub async fn patch_update_event(
|
||||||
|
headers: HeaderMap,
|
||||||
|
Extension(state): Extension<AppState>,
|
||||||
|
Path(id): Path<String>,
|
||||||
|
ValidatedJson(payload): ValidatedJson<EventsUpdateRequestDto>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
require_permissions!(headers, state, [PermissionsEnum::Administrator], {
|
||||||
|
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 = "[ADMIN] Soft delete event", body = MessageResponseDto)
|
||||||
|
),
|
||||||
|
tag = "Events"
|
||||||
|
)]
|
||||||
|
pub async fn delete_event(
|
||||||
|
headers: HeaderMap,
|
||||||
|
Extension(state): Extension<AppState>,
|
||||||
|
Path(id): Path<String>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
require_permissions!(headers, state, [PermissionsEnum::Administrator], {
|
||||||
|
EventsService::delete_event(&state, id).await
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
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, ValidationError};
|
||||||
|
|
||||||
|
// Custom URL validator that ensures valid HTTP/HTTPS URLs
|
||||||
|
pub fn validate_url(url: &str) -> Result<(), ValidationError> {
|
||||||
|
lazy_static! {
|
||||||
|
static ref VALID_URL_REGEX: Regex = Regex::new(r"^https?://[^\s$.?#].[^\s]*$").unwrap();
|
||||||
|
}
|
||||||
|
if VALID_URL_REGEX.is_match(url) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ValidationError::new("invalid_url"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom validator for future dates
|
||||||
|
pub fn validate_future_date(end_date: &DateTime<Utc>) -> Result<(), ValidationError> {
|
||||||
|
let now = Utc::now();
|
||||||
|
if end_date > &now {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ValidationError::new("future_date"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom validator for event date ranges (for combined validation)
|
||||||
|
pub fn validate_date_range(start_date: &DateTime<Utc>, end_date: &DateTime<Utc>) -> Result<(), ValidationError> {
|
||||||
|
if start_date <= end_date {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ValidationError::new("date_range"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||||
|
pub struct EventsCreateRequestDto {
|
||||||
|
#[validate(length(min = 1, max = 100, message = "Name must be between 1 and 100 characters"))]
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
#[validate(length(min = 1, max = 1000, message = "Description must be between 1 and 1000 characters"))]
|
||||||
|
pub description: String,
|
||||||
|
|
||||||
|
#[validate(custom(
|
||||||
|
function = "validate_url",
|
||||||
|
message = "Detail link must be a valid HTTP/HTTPS URL"
|
||||||
|
))]
|
||||||
|
pub detail_link: String,
|
||||||
|
|
||||||
|
#[validate(range(min = 0.0, max = 1_000_000.0, message = "Price must be between 0 and 1,000,000"))]
|
||||||
|
pub price: f64,
|
||||||
|
|
||||||
|
#[schema(example = "2025-09-20T13:00:00Z", value_type = String)]
|
||||||
|
#[validate(custom(
|
||||||
|
function = "validate_future_date",
|
||||||
|
message = "End date must be in the future"
|
||||||
|
))]
|
||||||
|
pub end_date: DateTime<Utc>,
|
||||||
|
|
||||||
|
#[schema(example = "2025-09-20T13:00:00Z", value_type = String)]
|
||||||
|
pub start_date: DateTime<Utc>,
|
||||||
|
|
||||||
|
#[validate(length(max = 200, message = "Location name cannot exceed 200 characters"))]
|
||||||
|
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>,
|
||||||
|
|
||||||
|
#[validate(range(min = 0.0, message = "Price cannot be negative"))]
|
||||||
|
pub price: f64,
|
||||||
|
pub is_online: bool,
|
||||||
|
pub description: String,
|
||||||
|
#[validate(url(message = "Detail link must be a valid URL"))]
|
||||||
|
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,167 @@
|
|||||||
|
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, make_thing};
|
||||||
|
use std::time::Instant;
|
||||||
|
use tracing::instrument;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
pub struct EventsRepository<'a> {
|
||||||
|
state: &'a AppState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> EventsRepository<'a> {
|
||||||
|
pub fn new(state: &'a AppState) -> Self {
|
||||||
|
Self { state }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self, meta), err)]
|
||||||
|
pub async fn query_event_list(
|
||||||
|
&self,
|
||||||
|
meta: MetaRequestDto,
|
||||||
|
) -> Result<ResponseListSuccessDto<Vec<EventsQueryDto>>> {
|
||||||
|
let now = Instant::now();
|
||||||
|
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();
|
||||||
|
info!(query = %query, "Executing SurrealDB query");
|
||||||
|
let res: Vec<EventsQueryDto> =
|
||||||
|
self.state.surrealdb_ws.query(query).await?.take(0)?;
|
||||||
|
let elapsed = now.elapsed();
|
||||||
|
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
|
||||||
|
== "development"
|
||||||
|
{
|
||||||
|
println!("Query 'query_event_list' took: {elapsed:.2?}");
|
||||||
|
}
|
||||||
|
let data = ResponseListSuccessDto {
|
||||||
|
data: res,
|
||||||
|
meta: None,
|
||||||
|
};
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self, id), err)]
|
||||||
|
pub async fn query_event_by_id(&self, id: String) -> Result<EventsQueryDto> {
|
||||||
|
let now = Instant::now();
|
||||||
|
let db = &self.state.surrealdb_ws;
|
||||||
|
// Attempt to parse the ID. If it's a full Thing (e.g., "events:some_id"), extract the ID part.
|
||||||
|
// Otherwise, assume it's already the raw ID.
|
||||||
|
let parsed_id = if id.contains(":") {
|
||||||
|
let thing = make_thing(ResourceEnum::Events.to_string().as_str(), &id);
|
||||||
|
get_id(&thing)?.1.to_string()
|
||||||
|
} else {
|
||||||
|
id.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let builder = DetailQueryBuilder::new(ResourceEnum::Events.to_string())
|
||||||
|
.with_id(&parsed_id)
|
||||||
|
.with_select_fields(vec!["*"]);
|
||||||
|
let sql = builder.build();
|
||||||
|
info!(query = %sql, "Executing SurrealDB query");
|
||||||
|
let result: Option<EventsQueryDto> =
|
||||||
|
builder.apply_bindings(db.query(sql)).await?.take(0)?;
|
||||||
|
let elapsed = now.elapsed();
|
||||||
|
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
|
||||||
|
== "development"
|
||||||
|
{
|
||||||
|
println!("Query 'query_event_by_id' took: {elapsed:.2?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Some(event) => {
|
||||||
|
if event.is_deleted {
|
||||||
|
bail!("Event not found");
|
||||||
|
}
|
||||||
|
Ok(event)
|
||||||
|
}
|
||||||
|
None => bail!("Event not found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self, data), err)]
|
||||||
|
pub async fn query_create_event(&self, data: EventsSchema) -> Result<String> {
|
||||||
|
let now = Instant::now();
|
||||||
|
let db = &self.state.surrealdb_ws;
|
||||||
|
let query_str = format!("CREATE {} CONTENT ...", ResourceEnum::Events);
|
||||||
|
info!(query = %query_str, "Executing SurrealDB query");
|
||||||
|
let record: Option<EventsSchema> = db
|
||||||
|
.create(ResourceEnum::Events.to_string())
|
||||||
|
.content(data)
|
||||||
|
.await?;
|
||||||
|
let elapsed = now.elapsed();
|
||||||
|
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
|
||||||
|
== "development"
|
||||||
|
{
|
||||||
|
println!("Query 'query_create_event' took: {elapsed:.2?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
match record {
|
||||||
|
Some(_) => Ok("Success create event".into()),
|
||||||
|
None => bail!("Failed to create event"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self, data), err)]
|
||||||
|
pub async fn query_update_event(&self, data: EventsSchema) -> Result<String> {
|
||||||
|
let now = Instant::now();
|
||||||
|
let db = &self.state.surrealdb_ws;
|
||||||
|
|
||||||
|
let existing = self.query_event_by_id(data.id.id.to_raw()).await?;
|
||||||
|
if existing.is_deleted {
|
||||||
|
bail!("Event already deleted");
|
||||||
|
}
|
||||||
|
|
||||||
|
let merged = EventsSchema {
|
||||||
|
created_at: existing.created_at,
|
||||||
|
updated_at: get_iso_date(),
|
||||||
|
..data
|
||||||
|
};
|
||||||
|
|
||||||
|
let record_key = get_id(&merged.id)?;
|
||||||
|
let query_str = format!("UPDATE {:?} MERGE ...", record_key);
|
||||||
|
info!(query = %query_str, "Executing SurrealDB query");
|
||||||
|
let record: Option<EventsSchema> = db.update(record_key).merge(merged).await?;
|
||||||
|
let elapsed = now.elapsed();
|
||||||
|
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
|
||||||
|
== "development"
|
||||||
|
{
|
||||||
|
println!("Query 'query_update_event' took: {elapsed:.2?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
match record {
|
||||||
|
Some(_) => Ok("Success update event".into()),
|
||||||
|
None => bail!("Failed to update event"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self, id), err)]
|
||||||
|
pub async fn query_delete_event(&self, id: String) -> Result<String> {
|
||||||
|
let now = Instant::now();
|
||||||
|
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 query_str = format!("UPDATE {:?} MERGE {{ is_deleted: true }}", record_key);
|
||||||
|
info!(query = %query_str, "Executing SurrealDB query");
|
||||||
|
let record: Option<EventsSchema> = db
|
||||||
|
.update(record_key)
|
||||||
|
.merge(serde_json::json!({ "is_deleted": true }))
|
||||||
|
.await?;
|
||||||
|
let elapsed = now.elapsed();
|
||||||
|
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
|
||||||
|
== "development"
|
||||||
|
{
|
||||||
|
println!("Query 'query_delete_event' took: {elapsed:.2?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
match record {
|
||||||
|
Some(_) => Ok("Success delete event".into()),
|
||||||
|
None => bail!("Failed to delete event"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
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::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,101 @@
|
|||||||
|
use super::{
|
||||||
|
events_dto::{
|
||||||
|
EventsCreateRequestDto, EventsDetailItemDto, EventsListItemDto, EventsQueryDto,
|
||||||
|
EventsUpdateRequestDto,
|
||||||
|
},
|
||||||
|
events_repository::EventsRepository,
|
||||||
|
events_schema::EventsSchema,
|
||||||
|
};
|
||||||
|
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 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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
// Export only the necessary public items
|
||||||
|
pub use events_dto::{
|
||||||
|
EventsCreateRequestDto,
|
||||||
|
EventsUpdateRequestDto,
|
||||||
|
EventsListItemDto,
|
||||||
|
EventsDetailItemDto,
|
||||||
|
};
|
||||||
|
pub use events_controller::{
|
||||||
|
get_event_list,
|
||||||
|
get_event_by_id,
|
||||||
|
post_create_event,
|
||||||
|
patch_update_event,
|
||||||
|
delete_event,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn events_public_routes() -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route("/cms/landing/events", get(get_event_list))
|
||||||
|
.route("/cms/landing/events/detail/{id}", get(get_event_by_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn events_protected_routes() -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route("/cms/landing/events/create", post(post_create_event))
|
||||||
|
.route("/cms/landing/events/update/{id}", patch(patch_update_event))
|
||||||
|
.route("/cms/landing/events/delete/{id}", delete(delete_event))
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
pub mod events;
|
||||||
|
pub mod testimonials;
|
||||||
|
|
||||||
|
pub use events::events_public_routes;
|
||||||
|
pub use events::events_protected_routes;
|
||||||
|
pub use testimonials::testimonials_public_routes;
|
||||||
|
pub use testimonials::testimonials_protected_routes;
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
// Export only the necessary public items
|
||||||
|
pub use testimonials_dto::{
|
||||||
|
TestimonialsCreateRequestDto,
|
||||||
|
TestimonialsUpdateRequestDto,
|
||||||
|
TestimonialsListItemDto,
|
||||||
|
TestimonialsDetailItemDto,
|
||||||
|
};
|
||||||
|
pub use testimonials_controller::{
|
||||||
|
get_testimonial_list,
|
||||||
|
get_testimonial_by_id,
|
||||||
|
post_create_testimonial,
|
||||||
|
patch_update_testimonial,
|
||||||
|
delete_testimonial,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn testimonials_public_routes() -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route("/cms/landing/testimonials", get(get_testimonial_list))
|
||||||
|
.route("/cms/landing/testimonials/detail/{id}", get(get_testimonial_by_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn testimonials_protected_routes() -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route("/cms/landing/testimonials/create", post(post_create_testimonial))
|
||||||
|
.route("/cms/landing/testimonials/update/{id}", patch(patch_update_testimonial))
|
||||||
|
.route("/cms/landing/testimonials/delete/{id}", delete(delete_testimonial))
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
use super::{
|
||||||
|
testimonials_dto::{
|
||||||
|
TestimonialsCreateRequestDto, TestimonialsDetailItemDto,
|
||||||
|
TestimonialsListItemDto, TestimonialsUpdateRequestDto,
|
||||||
|
},
|
||||||
|
testimonials_service::TestimonialsService,
|
||||||
|
};
|
||||||
|
use axum::extract::{Path, Query};
|
||||||
|
use axum::response::IntoResponse;
|
||||||
|
use axum::{Extension, http::HeaderMap};
|
||||||
|
use imphnen_iam::{UsersDetailQueryDto, require_auth};
|
||||||
|
use imphnen_libs::{
|
||||||
|
AppState, MessageResponseDto, MetaRequestDto, ResponseListSuccessDto,
|
||||||
|
ResponseSuccessDto, ValidatedJson,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[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 = "[PUBLIC] 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 = "[PUBLIC] 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 = "[USER] Create new testimonial", body = MessageResponseDto)
|
||||||
|
),
|
||||||
|
tag = "Testimonials"
|
||||||
|
)]
|
||||||
|
pub async fn post_create_testimonial(
|
||||||
|
headers: HeaderMap,
|
||||||
|
Extension(state): Extension<AppState>,
|
||||||
|
Extension(authenticated_user): Extension<UsersDetailQueryDto>,
|
||||||
|
ValidatedJson(payload): ValidatedJson<TestimonialsCreateRequestDto>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
require_auth!(headers, state, {
|
||||||
|
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 = "[USER] Update testimonial", body = MessageResponseDto)
|
||||||
|
),
|
||||||
|
tag = "Testimonials"
|
||||||
|
)]
|
||||||
|
pub async fn patch_update_testimonial(
|
||||||
|
headers: HeaderMap,
|
||||||
|
Path(id): Path<String>,
|
||||||
|
Extension(state): Extension<AppState>,
|
||||||
|
Extension(authenticated_user): Extension<UsersDetailQueryDto>,
|
||||||
|
ValidatedJson(payload): ValidatedJson<TestimonialsUpdateRequestDto>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
require_auth!(headers, state, {
|
||||||
|
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 = "[USER] Soft delete testimonial", body = MessageResponseDto)
|
||||||
|
),
|
||||||
|
tag = "Testimonials"
|
||||||
|
)]
|
||||||
|
pub async fn delete_testimonial(
|
||||||
|
headers: HeaderMap,
|
||||||
|
Extension(state): Extension<AppState>,
|
||||||
|
Extension(authenticated_user): Extension<UsersDetailQueryDto>,
|
||||||
|
Path(id): Path<String>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
require_auth!(headers, state, {
|
||||||
|
TestimonialsService::delete_testimonial(&state, id, &authenticated_user).await
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
use imphnen_iam::v1::users::UsersSchema;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use surrealdb::sql::Thing;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
use validator::{Validate, ValidationError};
|
||||||
|
|
||||||
|
// Custom validator for content length and format
|
||||||
|
pub fn validate_testimonial_content(content: &str) -> Result<(), ValidationError> {
|
||||||
|
lazy_static! {
|
||||||
|
static ref CONTENT_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9\s.,!?'-]+$").unwrap();
|
||||||
|
}
|
||||||
|
if CONTENT_REGEX.is_match(content) && content.len() <= 1000 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ValidationError::new("invalid_content"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||||
|
pub struct TestimonialsCreateRequestDto {
|
||||||
|
#[validate(length(min = 1, max = 100, message = "Role must be between 1 and 100 characters"))]
|
||||||
|
pub role: String,
|
||||||
|
|
||||||
|
#[validate(length(
|
||||||
|
min = 1,
|
||||||
|
max = 1000,
|
||||||
|
message = "Content must be between 1 and 1000 characters"
|
||||||
|
))]
|
||||||
|
#[validate(custom(
|
||||||
|
function = "validate_testimonial_content",
|
||||||
|
message = "Content contains invalid characters or is too long"
|
||||||
|
))]
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||||
|
pub struct TestimonialsUpdateRequestDto {
|
||||||
|
#[validate(length(min = 1, max = 100, message = "Role must be between 1 and 100 characters"))]
|
||||||
|
pub role: String,
|
||||||
|
|
||||||
|
#[validate(length(
|
||||||
|
min = 1,
|
||||||
|
max = 1000,
|
||||||
|
message = "Content must be between 1 and 1000 characters"
|
||||||
|
))]
|
||||||
|
#[validate(custom(
|
||||||
|
function = "validate_testimonial_content",
|
||||||
|
message = "Content contains invalid characters or is too long"
|
||||||
|
))]
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
|
pub struct TestimonialsListItemDto {
|
||||||
|
pub id: String,
|
||||||
|
pub user_id: String,
|
||||||
|
pub user_fullname: String,
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
role: self.role,
|
||||||
|
content: self.content,
|
||||||
|
created_at: self.created_at,
|
||||||
|
is_deleted: self.is_deleted,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user