Compare commits
161
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b5efeff87 | ||
|
|
1d34d29b0b | ||
|
|
7d1078f52a | ||
|
|
67d3f2fced | ||
|
|
b68e362a02 | ||
|
|
db44c5a51f | ||
|
|
6570bbf752 | ||
|
|
5667a0d608 | ||
|
|
a4bbc73c7e | ||
|
|
729335014f | ||
|
|
852e9652ee | ||
|
|
f225ee8969 | ||
|
|
4ee00f1fe5 | ||
|
|
d014a94ea4 | ||
|
|
e1bc336baa | ||
|
|
8144ab40e9 | ||
|
|
76201d8d3e | ||
|
|
c38b718eb4 | ||
|
|
222495472a | ||
|
|
12db8cc6b4 | ||
|
|
331a4a4e88 | ||
|
|
2ae43b3bcc | ||
|
|
4bba182ea3 | ||
|
|
5715e75593 | ||
|
|
11442c6285 | ||
|
|
05a5b39195 | ||
|
|
052fee4b78 | ||
|
|
c825a78c6f | ||
|
|
c4280e9953 | ||
|
|
30128a8fe2 | ||
|
|
e432a1a743 | ||
|
|
1b3366d735 | ||
|
|
b429b3a9c7 | ||
|
|
6fe495eed1 | ||
|
|
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 |
@@ -0,0 +1,2 @@
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
linker = "rust-lld.exe"
|
||||
+33
-8
@@ -1,8 +1,33 @@
|
||||
PORT=
|
||||
SURREALDB_URL=
|
||||
SURREALDB_USERNAME=
|
||||
SURREALDB_PASSWORD=
|
||||
SURREALDB_NAMESPACE=
|
||||
SURREALDB_DBNAME=
|
||||
ACCESS_TOKEN_SECRET=
|
||||
REFRESH_TOKEN_SECRET=
|
||||
RUST_ENV=development
|
||||
RUST_LOG=debug
|
||||
PORT=4099
|
||||
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"
|
||||
POOL_SIZE=10
|
||||
CONNECT_TIMEOUT=30
|
||||
IDLE_TIMEOUT=60
|
||||
MAX_LIFETIME=1800
|
||||
STATEMENT_TIMEOUT=30000
|
||||
IDLE_IN_TRANSACTION_SESSION_TIMEOUT=60000
|
||||
SSLMODE=require
|
||||
RETRY_ATTEMPTS=3
|
||||
RETRY_DELAY=1
|
||||
GOOGLE_REDIRECT_URL=http://localhost:8000/api/v1/auth/google/callback
|
||||
|
||||
CDN_URL=https://cdn.asepharyana.tech
|
||||
CORS_ALLOWED_ORIGINS=http://localhost:3000,https://gacha.imphnen.dev,https://imphnen.dev,https://dimentorin.imphnen.dev
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
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"
|
||||
@@ -1,65 +0,0 @@
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- name: Build the project
|
||||
run: cargo build --release
|
||||
|
||||
- name: Stop service on VPS before upload
|
||||
uses: appleboy/ssh-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.VPS_IP }}
|
||||
username: ${{ secrets.VPS_USER }}
|
||||
key: ${{ secrets.VPS_SSH_KEY }}
|
||||
port: ${{ secrets.VPS_PORT }}
|
||||
script: |
|
||||
set -e
|
||||
echo "Stopping the service before uploading the binary"
|
||||
sudo systemctl stop imphnen-backend-service
|
||||
|
||||
- name: Upload artifact to VPS
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.VPS_IP }}
|
||||
username: ${{ secrets.VPS_USER }}
|
||||
key: ${{ secrets.VPS_SSH_KEY }}
|
||||
port: ${{ secrets.VPS_PORT }}
|
||||
source: ./target/release/*
|
||||
target: /opt/imphnen-backend-service/imphnen-backend-service
|
||||
rm: true
|
||||
overwrite: true
|
||||
|
||||
- name: Deploy to server
|
||||
uses: appleboy/ssh-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.VPS_IP }}
|
||||
username: ${{ secrets.VPS_USER }}
|
||||
key: ${{ secrets.VPS_SSH_KEY }}
|
||||
port: ${{ secrets.VPS_PORT }}
|
||||
script: |
|
||||
set -e
|
||||
|
||||
echo "Restarting the service"
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
sudo systemctl restart imphnen-backend-service
|
||||
|
||||
echo "Deployment completed successfully"
|
||||
@@ -0,0 +1,78 @@
|
||||
name: Nix Build & Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['develop']
|
||||
pull_request:
|
||||
branches: ['develop']
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
|
||||
- name: Setup Cachix
|
||||
uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: msdqn
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
|
||||
- name: Build
|
||||
run: nix build .#default -o result
|
||||
|
||||
- name: Push to Cachix
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
|
||||
run: cachix push msdqn result
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/develop'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
|
||||
- name: Setup SSH
|
||||
env:
|
||||
INFRA_DEPLOY_KEY: ${{ secrets.INFRA_DEPLOY_KEY }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${INFRA_DEPLOY_KEY}" > ~/.ssh/deploy_key
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null
|
||||
ssh-keyscan 167.235.70.37 >> ~/.ssh/known_hosts 2>/dev/null
|
||||
|
||||
- name: Update infra flake.lock
|
||||
run: |
|
||||
export GIT_SSH_COMMAND="ssh -i ~/.ssh/deploy_key -o IdentitiesOnly=yes"
|
||||
git clone git@github.com:IMPHNEN/imphnen-infrastructure.git /tmp/infra
|
||||
cd /tmp/infra
|
||||
nix flake update imphnen-backend
|
||||
if git diff --quiet flake.lock; then
|
||||
echo "flake.lock unchanged, skipping"
|
||||
else
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add flake.lock
|
||||
git commit -m "chore: update imphnen-backend-service to ${GITHUB_SHA::7}"
|
||||
git push
|
||||
fi
|
||||
|
||||
- name: Deploy to server
|
||||
run: |
|
||||
ssh -i ~/.ssh/deploy_key \
|
||||
-o ConnectTimeout=30 \
|
||||
-o StrictHostKeyChecking=accept-new \
|
||||
-o ServerAliveInterval=30 \
|
||||
-o ServerAliveCountMax=40 \
|
||||
root@167.235.70.37 \
|
||||
'nixos-rebuild switch --flake github:IMPHNEN/imphnen-infrastructure#hetzner --refresh 2>&1 | tail -50'
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: rm -f ~/.ssh/deploy_key
|
||||
@@ -1,19 +0,0 @@
|
||||
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
|
||||
@@ -13,3 +13,4 @@
|
||||
.env.development
|
||||
.env.staging
|
||||
.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
+2314
-2431
File diff suppressed because it is too large
Load Diff
+79
-23
@@ -1,49 +1,105 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"tests",
|
||||
"imphnen-entities",
|
||||
"imphnen-macros",
|
||||
"imphnen-libs",
|
||||
"imphnen-storage",
|
||||
"imphnen-email",
|
||||
"imphnen-utils",
|
||||
"imphnen-middleware",
|
||||
"imphnen-iam",
|
||||
"imphnen-cms",
|
||||
"imphnen-libs",
|
||||
"imphnen-utils",
|
||||
"imphnen-gacha",
|
||||
"imphnen-dimentorin",
|
||||
"imphnen-hackathon",
|
||||
"imphnen-gateway",
|
||||
"imphnen-backend",
|
||||
"imphnen-entities",
|
||||
"imphnen-dimentorin",
|
||||
"imphnen-middleware",
|
||||
]
|
||||
|
||||
|
||||
[workspace.dependencies]
|
||||
axum = { version = "0.8.4", features = ["multipart"] }
|
||||
log = "0.4.25"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
serde_json = "1.0.138"
|
||||
tokio = { version = "1.45.0" }
|
||||
async-trait = "0.1.83"
|
||||
oauth2 = "5.0.0"
|
||||
reqwest = { version = "0.12.23", features = ["json"] }
|
||||
serde_json = "1.0.142"
|
||||
axum = { version = "0.8.4", features = ["multipart", "macros"] }
|
||||
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"] }
|
||||
jsonwebtoken = "9.3.1"
|
||||
chrono = "0.4.41"
|
||||
utoipa = { version = "5.3.1", features = ["axum_extras"] }
|
||||
utoipa-swagger-ui = { version = "9.0.0", features = ["axum"] }
|
||||
lettre = { version = "0.11.16", features = ["tokio1-native-tls"] }
|
||||
surrealdb = { version = "2.3.2", features = ["kv-mem"] }
|
||||
thiserror = "2.0.12"
|
||||
anyhow = "1.0.98"
|
||||
rand = { version = "0.9.1", features = ["std", "alloc"] }
|
||||
utoipa = { version = "5.4.0", features = ["axum_extras", "uuid", "chrono"] }
|
||||
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
|
||||
lettre = { version = "0.11.18", features = ["tokio1-native-tls"] }
|
||||
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.4", features = ["cors"] }
|
||||
validator = { version = "0.12", features = ["derive"] }
|
||||
lazy_static = "1.4.0"
|
||||
tower-http = { version = "0.6.6", features = ["cors", "trace"] }
|
||||
http-body-util = "0.1.3"
|
||||
zod-rs = { version = "0.4", features = ["macros"] }
|
||||
zod-rs-util = "0.4"
|
||||
paginator-rs = "0.2"
|
||||
paginator-utils = "0.2"
|
||||
paginator-sea-orm = { version = "0.2", features = ["sqlx-postgres", "runtime-tokio"] }
|
||||
paginator-axum = "0.2"
|
||||
lazy_static = "1.5.0"
|
||||
regex = "1.11.1"
|
||||
axum-test = "17.2.0"
|
||||
fancy-regex = "0.14.0"
|
||||
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"
|
||||
sea-orm = { version = "1.1", features = ["sqlx-postgres", "runtime-tokio-native-tls", "macros", "with-chrono", "uuid"] }
|
||||
sqlx = { version = "0.8", features = ["postgres", "runtime-tokio-native-tls", "uuid", "chrono", "json", "macros"] }
|
||||
num_cpus = "1.16.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"
|
||||
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-middleware = { path = "./imphnen-middleware" }
|
||||
imphnen-macros = { path = "./imphnen-macros" }
|
||||
imphnen-hackathon = { path = "./imphnen-hackathon" }
|
||||
imphnen-storage = { path = "./imphnen-storage" }
|
||||
imphnen-email = { path = "./imphnen-email" }
|
||||
bcrypt = "0.15"
|
||||
image = { version = "0.25", features = ["png", "jpeg"] }
|
||||
qrcode = { version = "0.14", default-features = false, features = ["image"] }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
opt-level = "z"
|
||||
|
||||
|
||||
+21
-13
@@ -1,4 +1,4 @@
|
||||
FROM rust:1.85-alpine AS builder
|
||||
FROM rust:1.86-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache \
|
||||
curl \
|
||||
@@ -11,30 +11,38 @@ WORKDIR /app
|
||||
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
|
||||
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 && \
|
||||
RUN mkdir -p \
|
||||
imphnen-backend/src \
|
||||
imphnen-cms/src \
|
||||
imphnen-dimentorin/src \
|
||||
imphnen-email/src \
|
||||
imphnen-entities/src \
|
||||
imphnen-gacha/src \
|
||||
imphnen-gateway/src \
|
||||
imphnen-hackathon/src \
|
||||
imphnen-iam/src \
|
||||
imphnen-libs/src \
|
||||
imphnen-macros/src \
|
||||
imphnen-middleware/src \
|
||||
imphnen-storage/src \
|
||||
imphnen-utils/src && \
|
||||
echo "fn main() {}" > imphnen-backend/src/main.rs && \
|
||||
find . -name "src" -type d -exec sh -c 'echo "// dummy" > "$1/lib.rs"' _ {} \;
|
||||
|
||||
RUN echo '[package]\nname = "tests"\nversion = "0.1.0"\nedition = "2021"' > tests/Cargo.toml
|
||||
|
||||
|
||||
RUN echo -e '[package]\nname = "tests"\nversion = "0.1.0"\nedition = "2021"' > tests/Cargo.toml
|
||||
|
||||
find . -name "src" -type d -exec sh -c 'touch "$1/lib.rs"' _ {} \;
|
||||
|
||||
COPY imphnen-backend ./imphnen-backend
|
||||
COPY imphnen-cms ./imphnen-cms
|
||||
COPY imphnen-dimentorin ./imphnen-dimentorin
|
||||
COPY imphnen-email ./imphnen-email
|
||||
COPY imphnen-entities ./imphnen-entities
|
||||
COPY imphnen-gacha ./imphnen-gacha
|
||||
COPY imphnen-gateway ./imphnen-gateway
|
||||
COPY imphnen-hackathon ./imphnen-hackathon
|
||||
COPY imphnen-iam ./imphnen-iam
|
||||
COPY imphnen-libs ./imphnen-libs
|
||||
COPY imphnen-macros ./imphnen-macros
|
||||
COPY imphnen-middleware ./imphnen-middleware
|
||||
COPY imphnen-storage ./imphnen-storage
|
||||
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 && \
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
@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
|
||||
@@ -1,30 +0,0 @@
|
||||
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"
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/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"
|
||||
+19
-9
@@ -1,15 +1,25 @@
|
||||
{pkgs ? import <nixpkgs> {}}: let
|
||||
manifest = (pkgs.lib.importTOML ./Cargo.toml).package;
|
||||
rustDeps = pkgs.callPackage ./Cargo.nix {};
|
||||
packageEntry = rustDeps.workspaceMembers.${manifest.name};
|
||||
deps = packageEntry.build.cargoDeps or null;
|
||||
{ pkgs ? import <nixpkgs> { } }:
|
||||
let
|
||||
swaggerUi = pkgs.fetchurl {
|
||||
url = "https://github.com/swagger-api/swagger-ui/archive/refs/tags/v5.17.14.zip";
|
||||
hash = "sha256-SBJE0IEgl7Efuu73n3HZQrFxYX+cn5UU5jrL4T5xzNw=";
|
||||
};
|
||||
in
|
||||
pkgs.rustPlatform.buildRustPackage {
|
||||
pname = manifest.name;
|
||||
version = manifest.version;
|
||||
cargoDeps = deps;
|
||||
pname = "imphnen-backend";
|
||||
version = (pkgs.lib.importTOML ./imphnen-backend/Cargo.toml).package.version;
|
||||
src = pkgs.lib.cleanSource ./.;
|
||||
cargoLock.lockFile = ./Cargo.lock;
|
||||
nativeBuildInputs = [pkgs.openssl pkgs.pkg-config];
|
||||
cargoBuildFlags = [
|
||||
"--package"
|
||||
"imphnen-backend"
|
||||
"--bin"
|
||||
"api"
|
||||
];
|
||||
nativeBuildInputs = [ pkgs.pkg-config ];
|
||||
buildInputs = [ pkgs.openssl ];
|
||||
preBuild = ''
|
||||
export SWAGGER_UI_DOWNLOAD_URL="file://${swaggerUi}"
|
||||
'';
|
||||
doCheck = false;
|
||||
}
|
||||
|
||||
+35
-5
@@ -7,10 +7,40 @@ services:
|
||||
- "${PORT}:${PORT}"
|
||||
env_file: ".env"
|
||||
depends_on:
|
||||
- surrealdb
|
||||
- postgres
|
||||
|
||||
surrealdb:
|
||||
image: surrealdb/surrealdb:latest
|
||||
command: start --log trace --user root --pass root
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: imphnen_postgres
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-imphnen}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
|
||||
ports:
|
||||
- "8000:8000"
|
||||
- "${POSTGRES_PORT:-5432}:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-imphnen}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
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:
|
||||
postgres_data:
|
||||
minio_data:
|
||||
|
||||
+7
-6
@@ -1,19 +1,20 @@
|
||||
{pkgs, ...}: let
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
baseImage = pkgs.ociTools.pullImage {
|
||||
imageName = "ubuntu";
|
||||
tag = "latest";
|
||||
};
|
||||
in
|
||||
pkgs.dockerTools.buildImage {
|
||||
name = "imphnen-cms-api";
|
||||
name = "imphnen-backend-service";
|
||||
|
||||
fromImage = baseImage;
|
||||
|
||||
copyToRoot = pkgs.buildEnv {
|
||||
name = "imphnen-cms-api";
|
||||
name = "imphnen-backend-service";
|
||||
paths = [
|
||||
(pkgs.stdenv.mkDerivation {
|
||||
name = "imphnen-cms-api";
|
||||
name = "imphnen-backend-service";
|
||||
src = ./src;
|
||||
|
||||
buildInputs = [
|
||||
@@ -29,14 +30,14 @@ in
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
cp target/release/najm-course-api $out/bin/
|
||||
cp target/release/imphnen-backend-service $out/bin/
|
||||
'';
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
config = {
|
||||
Cmd = ["/bin/imphnen-cms-api"];
|
||||
Cmd = [ "/bin/imphnen-backend-service" ];
|
||||
WorkingDir = "/bin";
|
||||
};
|
||||
}
|
||||
|
||||
Generated
+3
-3
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1739020877,
|
||||
"narHash": "sha256-mIvECo/NNdJJ/bXjNqIh8yeoSjVLAuDuTUzAo7dzs8Y=",
|
||||
"lastModified": 1775036866,
|
||||
"narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a79cfe0ebd24952b580b1cf08cd906354996d547",
|
||||
"rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -5,26 +5,41 @@
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
};
|
||||
|
||||
outputs = {
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
}: let
|
||||
supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-darwin" "aarch64-linux"];
|
||||
pkgsFor = system:
|
||||
}:
|
||||
let
|
||||
supportedSystems = [
|
||||
"x86_64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
"aarch64-linux"
|
||||
];
|
||||
pkgsFor =
|
||||
system:
|
||||
import nixpkgs {
|
||||
inherit system;
|
||||
config = {
|
||||
allowUnfree = true;
|
||||
};
|
||||
config.allowUnfree = true;
|
||||
};
|
||||
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
||||
in {
|
||||
in
|
||||
{
|
||||
packages = forAllSystems (system: {
|
||||
default = (pkgsFor system).callPackage ./default.nix { };
|
||||
});
|
||||
|
||||
overlays.default = final: _prev: {
|
||||
imphnen-backend = final.callPackage ./default.nix { };
|
||||
};
|
||||
|
||||
nixosModules.backend = ./nixos-module.nix;
|
||||
|
||||
devShells = forAllSystems (system: {
|
||||
default = (pkgsFor system).callPackage ./shell.nix { };
|
||||
});
|
||||
|
||||
dockerImages = forAllSystems (system: {
|
||||
tryOutApi = (pkgsFor system).callPackage ./docker.nix { };
|
||||
});
|
||||
|
||||
@@ -1,24 +1,69 @@
|
||||
[package]
|
||||
name = "imphnen-backend"
|
||||
version = "0.1.0"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "api"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "create_schema"
|
||||
path = "src/bin/create_schema.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_test_data"
|
||||
path = "src/bin/seed_test_data.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "test_postgres"
|
||||
path = "src/bin/test_postgres.rs"
|
||||
|
||||
[dependencies]
|
||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||
imphnen-gateway = { version = "0.1.0", path = "../imphnen-gateway" }
|
||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||
imphnen-iam = { version = "0.1.0", path = "../imphnen-iam" }
|
||||
imphnen-cms = { version = "0.1.0", path = "../imphnen-cms" }
|
||||
sea-orm.workspace = true
|
||||
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
|
||||
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
|
||||
@@ -26,3 +71,6 @@ 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
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
use env_logger;
|
||||
use imphnen_gateway::gateway_service;
|
||||
use imphnen_libs::axum_init;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::init();
|
||||
axum_init(|surrealdb_ws, surrealdb_mem| async {
|
||||
gateway_service(surrealdb_ws, surrealdb_mem).await
|
||||
axum_init(|postgres_db| async {
|
||||
gateway_service(postgres_db).await
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
#![allow(clippy::all)]
|
||||
|
||||
use imphnen_libs::postgres::{PostgresConfig, PostgresConnection};
|
||||
use sea_orm::{ConnectionTrait, Statement};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let dry_run = args
|
||||
.iter()
|
||||
.any(|s| s == "--dry-run" || s == "--no-exec" || s == "--dry");
|
||||
let force = args.iter().any(|s| s == "--force" || s == "-f");
|
||||
|
||||
println!("🔎 Clear DB script - WARNING: This will remove data from tables\n");
|
||||
println!("Note: script now runs by default (no --yes required). To preview without executing, use --dry-run.\n");
|
||||
|
||||
let tables = vec![
|
||||
"gacha_claims",
|
||||
"gacha_rolls",
|
||||
"gacha_items",
|
||||
"gacha_credits",
|
||||
"audit_logs",
|
||||
"rate_limits",
|
||||
"testimonials",
|
||||
"events",
|
||||
"app_mentors",
|
||||
"app_sessions",
|
||||
"app_roles_permissions",
|
||||
"app_permissions",
|
||||
"app_roles",
|
||||
"app_users",
|
||||
];
|
||||
|
||||
let postgres_config = PostgresConfig::from_env()?;
|
||||
let pg_conn = PostgresConnection::new(postgres_config).await?;
|
||||
let db = &pg_conn.conn;
|
||||
|
||||
let mut existing_tables: Vec<&str> = vec![];
|
||||
for t in tables.iter() {
|
||||
let check_sql = format!(
|
||||
"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '{}') as exists;",
|
||||
t
|
||||
);
|
||||
let stmt = Statement::from_string(db.get_database_backend(), check_sql);
|
||||
if let Ok(Some(row)) = pg_conn.query_one(stmt).await {
|
||||
let exists_val: Option<bool> = row.try_get("", "exists").ok();
|
||||
if exists_val.unwrap_or(false) {
|
||||
existing_tables.push(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if existing_tables.is_empty() {
|
||||
println!("No configured tables found to clear - nothing to do.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let truncate_sql = format!(
|
||||
"TRUNCATE TABLE {} RESTART IDENTITY CASCADE;",
|
||||
existing_tables.join(", ")
|
||||
);
|
||||
|
||||
println!("The script will run the following SQL (on the DB configured by env vars):\n\n{}", truncate_sql);
|
||||
|
||||
let env_name = imphnen_libs::ENV.rust_env.clone();
|
||||
if env_name == "production" && !force {
|
||||
println!("Security: RUST_ENV=production; the script will NOT run without --force. Use --force to override.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if dry_run {
|
||||
println!("Dry run enabled. No changes applied. To execute, re-run without --dry-run or use --force (in production).");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("Executing truncate...\n");
|
||||
|
||||
let postgres_config = PostgresConfig::from_env()?;
|
||||
let pg_conn = PostgresConnection::new(postgres_config).await?;
|
||||
let db = &pg_conn.conn;
|
||||
|
||||
let stmt = Statement::from_string(db.get_database_backend(), truncate_sql);
|
||||
match pg_conn.execute(stmt).await {
|
||||
Ok(_) => println!("✅ Successfully cleared DB tables"),
|
||||
Err(e) => println!("❌ Failed to clear DB tables: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
#![allow(clippy::all)]
|
||||
|
||||
use imphnen_entities::seaorm::{auth, common, gacha};
|
||||
use imphnen_libs::postgres::PostgresConfig;
|
||||
use sea_orm::sea_query::Table;
|
||||
use sea_orm::{ConnectionTrait, Database, DbBackend, EntityTrait, Schema};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("🛠️ Creating database schema...");
|
||||
|
||||
let config = PostgresConfig::from_env()?;
|
||||
let db = Database::connect(&config.database_url).await?;
|
||||
let builder = db.get_database_backend();
|
||||
|
||||
println!(" Database connected. Creating/updating tables...");
|
||||
|
||||
drop_and_create_table(&db, builder, "app_roles", auth::roles::Entity).await?;
|
||||
drop_and_create_table(&db, builder, "app_permissions", auth::permissions::Entity)
|
||||
.await?;
|
||||
drop_and_create_table(&db, builder, "app_users", auth::users::Entity).await?;
|
||||
drop_and_create_table(
|
||||
&db,
|
||||
builder,
|
||||
"app_roles_permissions",
|
||||
auth::roles_permissions::Entity,
|
||||
)
|
||||
.await?;
|
||||
drop_and_create_table(&db, builder, "app_mentors", auth::mentors::Entity).await?;
|
||||
drop_and_create_table(&db, builder, "app_sessions", auth::sessions::Entity)
|
||||
.await?;
|
||||
drop_and_create_table(&db, builder, "app_articles", common::articles::Entity)
|
||||
.await?;
|
||||
|
||||
drop_and_create_table(&db, builder, "events", common::events::Entity).await?;
|
||||
drop_and_create_table(&db, builder, "testimonials", common::testimonials::Entity)
|
||||
.await?;
|
||||
drop_and_create_table(&db, builder, "audit_logs", common::audit_log::Entity)
|
||||
.await?;
|
||||
drop_and_create_table(&db, builder, "rate_limits", common::rate_limit::Entity)
|
||||
.await?;
|
||||
|
||||
drop_and_create_table(&db, builder, "gacha_credits", gacha::gacha_credits::Entity)
|
||||
.await?;
|
||||
drop_and_create_table(&db, builder, "gacha_items", gacha::gacha_items::Entity)
|
||||
.await?;
|
||||
drop_and_create_table(&db, builder, "gacha_rolls", gacha::gacha_rolls::Entity)
|
||||
.await?;
|
||||
drop_and_create_table(&db, builder, "gacha_claims", gacha::gacha_claims::Entity)
|
||||
.await?;
|
||||
|
||||
println!("✅ Schema creation completed.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn drop_and_create_table<E>(
|
||||
db: &sea_orm::DatabaseConnection,
|
||||
builder: DbBackend,
|
||||
name: &str,
|
||||
entity: E,
|
||||
) -> Result<(), Box<dyn std::error::Error>>
|
||||
where
|
||||
E: EntityTrait,
|
||||
{
|
||||
let schema = Schema::new(builder);
|
||||
|
||||
let drop_stmt = Table::drop().table(entity).if_exists().cascade().to_owned();
|
||||
db.execute(builder.build(&drop_stmt)).await?;
|
||||
println!(" Dropped table if exists: {}", name);
|
||||
|
||||
let mut create_stmt = schema.create_table_from_entity(entity);
|
||||
create_stmt.if_not_exists();
|
||||
|
||||
db.execute(builder.build(&create_stmt)).await?;
|
||||
println!(" ✅ Created table: {}", name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#![allow(clippy::all)]
|
||||
|
||||
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();
|
||||
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,40 @@
|
||||
#![allow(clippy::all)]
|
||||
use chrono::Utc;
|
||||
use imphnen_entities::seaorm::common::articles::{
|
||||
ActiveModel as ArticleActiveModel, Entity as ArticlesEntity,
|
||||
};
|
||||
use imphnen_libs::postgres::PostgresConfig;
|
||||
use sea_orm::{ActiveModelTrait, ActiveValue, Database};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = PostgresConfig::from_env()?;
|
||||
let db = Database::connect(&config.database_url).await?;
|
||||
|
||||
let articles = vec![
|
||||
("Cara Memulai Karier di UI/UX Design", "cara-memulai-karier-ui-ux-design", "UI/UX & Design", "Panduan lengkap untuk masuk ke dunia UI/UX design, dari skill yang dibutuhkan hingga portofolio.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. UI/UX design adalah bidang yang menjanjikan. Artikel ini membahas langkah awal memulai karier sebagai UI/UX designer, tools yang wajib dikuasai seperti Figma, serta cara membangun portofolio yang menarik bagi perekrut."),
|
||||
("Belajar Rust: Panduan Pemula 2026", "belajar-rust-panduan-pemula-2026", "Software/Web Dev", "Bahasa pemrograman Rust sedang naik daun. Pelajari konsep ownership dan borrow checker.", "Rust adalah bahasa pemrograman yang fokus pada performa dan keamanan memori. Dalam artikel ini kita membahas ownership, borrowing, dan cara setup environment Rust di Linux dan Windows, serta contoh project sederhana."),
|
||||
("Mengenal Machine Learning untuk Data Analyst", "mengenal-machine-learning-data-analyst", "Data & AI", "Peran Data Analyst berevolusi dengan hadirnya machine learning. Simak panduannya.", "Machine learning membuka peluang besar bagi data analyst. Artikel ini menjelaskan perbedaan data analysis dan machine learning, serta roadmap belajar dari Python, pandas, sampai scikit-learn."),
|
||||
];
|
||||
|
||||
for (title, slug, category, excerpt, content) in articles {
|
||||
let am = ArticleActiveModel {
|
||||
id: ActiveValue::Set(Uuid::new_v4()),
|
||||
title: ActiveValue::Set(title.to_string()),
|
||||
slug: ActiveValue::Set(slug.to_string()),
|
||||
category: ActiveValue::Set(category.to_string()),
|
||||
excerpt: ActiveValue::Set(excerpt.to_string()),
|
||||
content: ActiveValue::Set(content.to_string()),
|
||||
cover_url: ActiveValue::Set(None),
|
||||
author_name: ActiveValue::Set(Some("IMPHNEN Editorial".to_string())),
|
||||
is_published: ActiveValue::Set(true),
|
||||
created_at: ActiveValue::Set(Utc::now()),
|
||||
updated_at: ActiveValue::Set(Utc::now()),
|
||||
};
|
||||
am.insert(&db).await?;
|
||||
println!("✅ Inserted article: {}", slug);
|
||||
}
|
||||
println!("🟢 All articles seeded");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,26 +1,24 @@
|
||||
use imphnen_cms::v1::landing::events::events_schema::EventsSchema;
|
||||
use imphnen_utils::{get_iso_date, Env};
|
||||
#![allow(clippy::all)]
|
||||
|
||||
use chrono::Utc;
|
||||
use imphnen_entities::seaorm::common::events::{
|
||||
ActiveModel as EventsActiveModel, Entity as EventEntity,
|
||||
};
|
||||
use imphnen_libs::postgres::{PostgresConfig, PostgresConnection};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ActiveValue::Set, ColumnTrait, EntityTrait, QueryFilter,
|
||||
};
|
||||
use std::error::Error;
|
||||
use surrealdb::engine::any;
|
||||
use surrealdb::{opt::auth::Root, sql::Thing};
|
||||
use imphnen_libs::enviroment::load_env;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
load_env();
|
||||
let env = Env::new();
|
||||
let db = any::connect(&env.surrealdb_url).await?;
|
||||
db.signin(Root {
|
||||
username: &env.surrealdb_username,
|
||||
password: &env.surrealdb_password,
|
||||
})
|
||||
.await?;
|
||||
db.use_ns(env.surrealdb_namespace)
|
||||
.use_db(env.surrealdb_dbname)
|
||||
.await?;
|
||||
let postgres_config = PostgresConfig::from_env()?;
|
||||
let pg_conn = PostgresConnection::new(postgres_config).await?;
|
||||
let db = &pg_conn.conn;
|
||||
|
||||
let events = vec![
|
||||
(
|
||||
"e1a2b3c4-5d6e-7f8g-9h0i-1j2k3l4m5n6o",
|
||||
"Tech Conference 2025",
|
||||
"Annual technology conference featuring the latest innovations in software development, AI, and cloud computing.",
|
||||
"https://techconf2025.example.com",
|
||||
@@ -31,7 +29,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
"2025-06-17T18:00:00Z",
|
||||
),
|
||||
(
|
||||
"f2b3c4d5-6e7f-8g9h-0i1j-2k3l4m5n6o7p",
|
||||
"Online Web Development Workshop",
|
||||
"Comprehensive workshop covering modern web development frameworks including React, Vue, and Angular.",
|
||||
"https://webdev-workshop.example.com",
|
||||
@@ -42,7 +39,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
"2025-07-10T17:00:00Z",
|
||||
),
|
||||
(
|
||||
"g3c4d5e6-7f8g-9h0i-1j2k-3l4m5n6o7p8q",
|
||||
"Startup Pitch Competition",
|
||||
"Exciting competition where emerging startups present their innovative ideas to a panel of expert judges and investors.",
|
||||
"https://startup-pitch.example.com",
|
||||
@@ -53,7 +49,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
"2025-08-05T16:00:00Z",
|
||||
),
|
||||
(
|
||||
"h4d5e6f7-8g9h-0i1j-2k3l-4m5n6o7p8q9r",
|
||||
"Digital Marketing Masterclass",
|
||||
"Learn advanced digital marketing strategies, social media optimization, and data-driven marketing techniques.",
|
||||
"https://digital-marketing.example.com",
|
||||
@@ -63,29 +58,146 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
"2025-09-20T13:00:00Z",
|
||||
"2025-09-22T15:00:00Z",
|
||||
),
|
||||
(
|
||||
"Rust Programming Bootcamp",
|
||||
"Intensive 3-day bootcamp to master Rust fundamentals and advanced concepts.",
|
||||
"https://rust-bootcamp.example.com",
|
||||
200.0,
|
||||
Some("Bandung Digital Valley".to_string()),
|
||||
false,
|
||||
"2025-10-01T09:00:00Z",
|
||||
"2025-10-03T17:00:00Z",
|
||||
),
|
||||
(
|
||||
"AI & Machine Learning Summit",
|
||||
"Global summit discussing the future of AI and its impact on industries.",
|
||||
"https://ai-summit.example.com",
|
||||
300.0,
|
||||
Some("Bali Nusa Dua Convention Center".to_string()),
|
||||
false,
|
||||
"2025-11-15T08:00:00Z",
|
||||
"2025-11-17T18:00:00Z",
|
||||
),
|
||||
(
|
||||
"Cybersecurity Awareness Webinar",
|
||||
"Free webinar on best practices for personal and corporate cybersecurity.",
|
||||
"https://cybersecurity-webinar.example.com",
|
||||
0.0,
|
||||
None,
|
||||
true,
|
||||
"2025-12-05T14:00:00Z",
|
||||
"2025-12-05T16:00:00Z",
|
||||
),
|
||||
(
|
||||
"Cloud Computing Workshop",
|
||||
"Hands-on workshop on deploying scalable applications using AWS and Azure.",
|
||||
"https://cloud-workshop.example.com",
|
||||
120.0,
|
||||
None,
|
||||
true,
|
||||
"2026-01-20T10:00:00Z",
|
||||
"2026-01-22T15:00:00Z",
|
||||
),
|
||||
(
|
||||
"Blockchain for Finance",
|
||||
"Exploring the applications of blockchain technology in the financial sector.",
|
||||
"https://blockchain-finance.example.com",
|
||||
180.0,
|
||||
Some("Jakarta Ritz-Carlton".to_string()),
|
||||
false,
|
||||
"2026-02-10T09:00:00Z",
|
||||
"2026-02-11T17:00:00Z",
|
||||
),
|
||||
(
|
||||
"Game Development Jam",
|
||||
"48-hour game development marathon for indie developers.",
|
||||
"https://game-jam.example.com",
|
||||
50.0,
|
||||
Some("Yogyakarta Creative Hub".to_string()),
|
||||
false,
|
||||
"2026-03-15T18:00:00Z",
|
||||
"2026-03-17T18:00:00Z",
|
||||
),
|
||||
(
|
||||
"UX/UI Design Principles",
|
||||
"Masterclass on creating intuitive and user-friendly interfaces.",
|
||||
"https://uxui-design.example.com",
|
||||
90.0,
|
||||
None,
|
||||
true,
|
||||
"2026-04-05T13:00:00Z",
|
||||
"2026-04-07T16:00:00Z",
|
||||
),
|
||||
(
|
||||
"Data Science Fundamentals",
|
||||
"Introduction to data analysis, visualization, and statistical modeling.",
|
||||
"https://data-science.example.com",
|
||||
110.0,
|
||||
None,
|
||||
true,
|
||||
"2026-05-12T10:00:00Z",
|
||||
"2026-05-14T15:00:00Z",
|
||||
),
|
||||
(
|
||||
"IoT Innovation Expo",
|
||||
"Showcase of the latest Internet of Things devices and solutions.",
|
||||
"https://iot-expo.example.com",
|
||||
50.0,
|
||||
Some("Surabaya Expo Center".to_string()),
|
||||
false,
|
||||
"2026-06-20T09:00:00Z",
|
||||
"2026-06-22T18:00:00Z",
|
||||
),
|
||||
];
|
||||
|
||||
for (id, name, description, detail_link, price, location, is_online, start_date, end_date) in events {
|
||||
let event = EventsSchema {
|
||||
id: Thing::from(("app_events", id)),
|
||||
name: name.into(),
|
||||
description: description.into(),
|
||||
detail_link: detail_link.into(),
|
||||
for (
|
||||
name,
|
||||
description,
|
||||
detail_link,
|
||||
price,
|
||||
location,
|
||||
is_online,
|
||||
is_deleted: false,
|
||||
start_date: start_date.into(),
|
||||
end_date: end_date.into(),
|
||||
created_at: get_iso_date(),
|
||||
updated_at: get_iso_date(),
|
||||
};
|
||||
|
||||
db.create::<Option<EventsSchema>>(("app_events", id))
|
||||
.content(event)
|
||||
start_date_str,
|
||||
end_date_str,
|
||||
) in events
|
||||
{
|
||||
let existing = EventEntity::find()
|
||||
.filter(<EventEntity as EntityTrait>::Column::Name.eq(name))
|
||||
.one(db)
|
||||
.await?;
|
||||
if existing.is_some() {
|
||||
println!("ℹ️ Skipping (already exists): {name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
println!("✅ Inserted event: {} ({})", name, if is_online { "Online" } else { "In-person" });
|
||||
let uuid = Uuid::new_v4();
|
||||
let mut event_model: EventsActiveModel = Default::default();
|
||||
event_model.id = Set(uuid);
|
||||
event_model.name = Set(name.to_string());
|
||||
event_model.description = Set(description.to_string());
|
||||
event_model.detail_link = Set(detail_link.to_string());
|
||||
event_model.price = Set(price);
|
||||
event_model.is_online = Set(is_online);
|
||||
event_model.location = Set(location.clone());
|
||||
event_model.start_date = Set(
|
||||
chrono::DateTime::parse_from_rfc3339(start_date_str)?
|
||||
.with_timezone(&chrono::Utc),
|
||||
);
|
||||
event_model.end_date = Set(
|
||||
chrono::DateTime::parse_from_rfc3339(end_date_str)?
|
||||
.with_timezone(&chrono::Utc),
|
||||
);
|
||||
event_model.is_deleted = Set(false);
|
||||
event_model.created_at = Set(Utc::now());
|
||||
event_model.updated_at = Set(Utc::now());
|
||||
|
||||
event_model.insert(db).await?;
|
||||
|
||||
println!(
|
||||
"✅ Inserted event: {} ({})",
|
||||
name,
|
||||
if is_online { "Online" } else { "In-person" }
|
||||
);
|
||||
}
|
||||
|
||||
println!("✅ All Events seeded");
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#![allow(clippy::all)]
|
||||
|
||||
use imphnen_entities::seaorm::gacha::gacha_items::ActiveModel as GachaItemActiveModel;
|
||||
use imphnen_entities::seaorm::gacha::gacha_rolls::ActiveModel as GachaRollActiveModel;
|
||||
use imphnen_libs::postgres::{PostgresConfig, PostgresConnection};
|
||||
use sea_orm::ActiveModelTrait;
|
||||
use sea_orm::ActiveValue::Set;
|
||||
use sea_orm::ConnectionTrait;
|
||||
use std::error::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let config = PostgresConfig::from_env()?;
|
||||
let pg_conn = PostgresConnection::new(config).await?;
|
||||
let db = &pg_conn.conn;
|
||||
|
||||
let check_item_sql =
|
||||
"SELECT id FROM app_gacha_items WHERE item_code = 'ITEM_TEST_1' LIMIT 1";
|
||||
let item_result = pg_conn
|
||||
.query_one(sea_orm::Statement::from_string(
|
||||
db.get_database_backend(),
|
||||
check_item_sql,
|
||||
))
|
||||
.await?;
|
||||
let gacha_item_uuid = if let Some(ref row) = item_result {
|
||||
row.try_get("", "id")?
|
||||
} else {
|
||||
let _ = pg_conn
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
db.get_database_backend(),
|
||||
"DELETE FROM app_gacha_items WHERE item_code = 'ITEM_TEST_1'".to_string(),
|
||||
))
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let new_uuid = Uuid::new_v4();
|
||||
let mut item_model: GachaItemActiveModel = Default::default();
|
||||
item_model.id = Set(new_uuid);
|
||||
item_model.item_code = Set("ITEM_TEST_1".to_string());
|
||||
item_model.name = Set("Test Gacha Item".to_string());
|
||||
item_model.description = Set("Test item for gacha".to_string());
|
||||
item_model.rarity = Set("common".to_string());
|
||||
item_model.type_ = Set("item".to_string());
|
||||
item_model.category = Set("test".to_string());
|
||||
item_model.value = Set(1);
|
||||
item_model.weight = Set(1.0);
|
||||
item_model.stock = Set(10);
|
||||
item_model.is_limited = Set(false);
|
||||
item_model.created_at = Set(chrono::Utc::now());
|
||||
item_model.updated_at = Set(chrono::Utc::now());
|
||||
item_model.insert(db).await?;
|
||||
println!("Gacha Item seeded successfully!");
|
||||
new_uuid
|
||||
};
|
||||
|
||||
let gacha_roll_id = Uuid::new_v4();
|
||||
let mut roll_model: GachaRollActiveModel = Default::default();
|
||||
roll_model.id = Set(gacha_roll_id);
|
||||
roll_model.user_id = Set(Uuid::parse_str("c3b1d6a8-8d4f-4b36-b789-2e532ec7a7b2")?);
|
||||
roll_model.gacha_id = Set(Uuid::new_v4().to_string());
|
||||
roll_model.item_id = Set(gacha_item_uuid);
|
||||
roll_model.weight = Set(1.0);
|
||||
roll_model.quantity = Set(10);
|
||||
roll_model.is_deleted = Set(false);
|
||||
roll_model.created_at = Set(Some(chrono::Utc::now().naive_utc()));
|
||||
roll_model.updated_at = Set(Some(chrono::Utc::now().naive_utc()));
|
||||
roll_model.insert(db).await?;
|
||||
println!("Gacha Roll seeded successfully!");
|
||||
let gacha_roll_id = Uuid::new_v4();
|
||||
let mut roll_model: GachaRollActiveModel = Default::default();
|
||||
roll_model.id = Set(gacha_roll_id);
|
||||
roll_model.user_id = Set(Uuid::parse_str("c3b1d6a8-8d4f-4b36-b789-2e532ec7a7b2")?);
|
||||
roll_model.gacha_id = Set(Uuid::new_v4().to_string());
|
||||
roll_model.item_id = Set(gacha_item_uuid);
|
||||
roll_model.weight = Set(1.0);
|
||||
roll_model.quantity = Set(10);
|
||||
roll_model.is_deleted = Set(false);
|
||||
roll_model.created_at = Set(Some(chrono::Utc::now().naive_utc()));
|
||||
roll_model.updated_at = Set(Some(chrono::Utc::now().naive_utc()));
|
||||
roll_model.insert(db).await?;
|
||||
println!("✅ Gacha items and rolls seeded.");
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#![allow(clippy::all)]
|
||||
|
||||
use imphnen_entities::seaorm::auth::mentors::ActiveModel as MentorsActiveModel;
|
||||
use imphnen_entities::seaorm::auth::roles::{
|
||||
Column as RoleColumn, Entity as RoleEntity,
|
||||
};
|
||||
use imphnen_entities::seaorm::auth::users::ActiveModel as UsersActiveModel;
|
||||
use imphnen_libs::hash_password;
|
||||
use imphnen_libs::postgres::{PostgresConfig, PostgresConnection};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ActiveValue::Set, ColumnTrait, ConnectionTrait, EntityTrait,
|
||||
QueryFilter,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::error::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let config = PostgresConfig::from_env()?;
|
||||
let pg_conn = PostgresConnection::new(config).await?;
|
||||
let db = &pg_conn.conn;
|
||||
|
||||
let _ = pg_conn
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
db.get_database_backend(),
|
||||
"DELETE FROM app_mentors WHERE id = 'e6f78d23-83bf-5c2b-bcd4-001345678901'"
|
||||
.to_string(),
|
||||
))
|
||||
.await
|
||||
.ok();
|
||||
let _ = pg_conn
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
db.get_database_backend(),
|
||||
"DELETE FROM app_users WHERE email = 'mentor@example.com'".to_string(),
|
||||
))
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let role = RoleEntity::find()
|
||||
.filter(RoleColumn::Name.eq("Mentor"))
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or("Role 'Mentor' not found")?;
|
||||
|
||||
let user_id = Uuid::new_v4();
|
||||
let mut user_model: UsersActiveModel = Default::default();
|
||||
user_model.id = Set(user_id);
|
||||
user_model.email = Set("mentor@example.com".to_string());
|
||||
user_model.password_hash = Set(hash_password("password").unwrap());
|
||||
user_model.username = Set("mentor@example.com".to_string());
|
||||
user_model.first_name = Set(Some("Mentor".to_string()));
|
||||
user_model.last_name = Set(Some("User".to_string()));
|
||||
user_model.avatar_url = Set(Some("https://example.com/avatar.jpg".to_string()));
|
||||
user_model.is_active = Set(true);
|
||||
user_model.is_verified = Set(true);
|
||||
user_model.role_id = Set(Some(role.id));
|
||||
user_model.created_at = Set(chrono::Utc::now());
|
||||
user_model.updated_at = Set(chrono::Utc::now());
|
||||
user_model.insert(db).await?;
|
||||
|
||||
let mentor_id = Uuid::new_v4();
|
||||
let mut mentor_model: MentorsActiveModel = Default::default();
|
||||
mentor_model.id = Set(mentor_id);
|
||||
mentor_model.user_id = Set(user_id);
|
||||
mentor_model.industries = Set(Some(json!(["Software", "Education"])));
|
||||
mentor_model.expertise = Set(Some(json!(["Rust", "Microservices"])));
|
||||
mentor_model.languages = Set(Some(json!(["Indonesian", "English"])));
|
||||
mentor_model.current_company = Set(Some("PT Contoh".to_string()));
|
||||
mentor_model.current_role = Set(Some("Senior Backend Engineer".to_string()));
|
||||
mentor_model.years_of_experience = Set(Some(5));
|
||||
mentor_model.topics_of_interest =
|
||||
Set(Some(json!(["Rust Programming", "Backend Development"])));
|
||||
mentor_model.preferred_mentee_level = Set(Some("beginner".to_string()));
|
||||
mentor_model.preferred_mentoring_formats = Set(Some(json!(["online", "offline"])));
|
||||
mentor_model.availability_commitment = Set(Some(
|
||||
"2 jam per minggu untuk mentoring online dan offline".to_string(),
|
||||
));
|
||||
mentor_model.mentoring_rate = Set(Some(100000.0));
|
||||
mentor_model.status = Set(Some("verified".to_string()));
|
||||
mentor_model.is_deleted = Set(false);
|
||||
mentor_model.created_at = Set(chrono::Utc::now());
|
||||
mentor_model.updated_at = Set(chrono::Utc::now());
|
||||
mentor_model.insert(db).await?;
|
||||
println!("Mentor created successfully!");
|
||||
|
||||
println!("✅ Inserted mentor user: mentor@example.com");
|
||||
println!("✅ Mentor user seeded");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,25 +1,20 @@
|
||||
#![allow(clippy::all)]
|
||||
|
||||
use chrono::Utc;
|
||||
use imphnen_entities::seaorm::auth::permissions::ActiveModel as PermissionActiveModel;
|
||||
use imphnen_entities::seaorm::auth::permissions::Entity as PermissionEntity;
|
||||
use imphnen_iam::PermissionsEnum;
|
||||
use imphnen_libs::enviroment::load_env;
|
||||
use imphnen_utils::{get_iso_date, Env};
|
||||
use serde_json::json;
|
||||
use imphnen_libs::postgres::{PostgresConfig, PostgresConnection};
|
||||
use sea_orm::ActiveModelTrait;
|
||||
use sea_orm::ActiveValue::Set;
|
||||
use std::error::Error;
|
||||
use surrealdb::engine::any;
|
||||
use surrealdb::opt::auth::Root;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
load_env();
|
||||
|
||||
let env = Env::new();
|
||||
let db = any::connect(&env.surrealdb_url).await?;
|
||||
db.signin(Root {
|
||||
username: &env.surrealdb_username,
|
||||
password: &env.surrealdb_password,
|
||||
})
|
||||
.await?;
|
||||
db.use_ns(env.surrealdb_namespace)
|
||||
.use_db(env.surrealdb_dbname)
|
||||
.await?;
|
||||
let config = PostgresConfig::from_env()?;
|
||||
let pg_conn = PostgresConnection::new(config).await?;
|
||||
let db = &pg_conn.conn;
|
||||
|
||||
for permission in [
|
||||
PermissionsEnum::ReadListUsers,
|
||||
@@ -48,22 +43,37 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
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.to_string());
|
||||
let parsed_id =
|
||||
Uuid::parse_str(&permission.id()).unwrap_or_else(|_| Uuid::new_v4());
|
||||
|
||||
let existing = PermissionEntity::find_by_id(parsed_id).one(db).await?;
|
||||
if existing.is_some() {
|
||||
println!("ℹ️ Skipping (already exists): {permission}");
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut perm_model: PermissionActiveModel = Default::default();
|
||||
perm_model.id = Set(parsed_id);
|
||||
perm_model.name = Set(permission.to_string());
|
||||
perm_model.is_deleted = Set(false);
|
||||
perm_model.created_at = Set(Utc::now());
|
||||
perm_model.updated_at = Set(Utc::now());
|
||||
perm_model.insert(db).await?;
|
||||
println!("✅ Inserted: {permission}");
|
||||
}
|
||||
|
||||
println!("✅ All Permissions seeded");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
use imphnen_utils::{get_iso_date, Env};
|
||||
use serde_json::json;
|
||||
use chrono::Utc;
|
||||
use imphnen_entities::seaorm::auth::roles::{Entity as RoleEntity, RoleBuilder};
|
||||
use imphnen_libs::postgres::{PostgresConfig, PostgresConnection};
|
||||
use sea_orm::{ActiveModelTrait, ActiveValue::Set, EntityTrait};
|
||||
use std::error::Error;
|
||||
use surrealdb::opt::auth::Root;
|
||||
use imphnen_libs::enviroment::load_env;
|
||||
use surrealdb::engine::any;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
load_env();
|
||||
let env = Env::new();
|
||||
let db = any::connect(&env.surrealdb_url).await?;
|
||||
db.signin(Root {
|
||||
username: &env.surrealdb_username,
|
||||
password: &env.surrealdb_password,
|
||||
})
|
||||
.await?;
|
||||
db.use_ns(env.surrealdb_namespace)
|
||||
.use_db(env.surrealdb_dbname)
|
||||
.await?;
|
||||
let config = PostgresConfig::from_env()?;
|
||||
let pg_conn = PostgresConnection::new(config).await?;
|
||||
let db = &pg_conn.conn;
|
||||
|
||||
let roles = vec![
|
||||
(
|
||||
@@ -28,7 +21,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
(
|
||||
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||
"User",
|
||||
None,
|
||||
Some("2025-02-28T14:53:58.576688+00"),
|
||||
Some("2025-02-28T14:53:58.576688+00"),
|
||||
),
|
||||
(
|
||||
@@ -46,26 +39,40 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
(
|
||||
"f6b03f25-e416-4893-ac88-caaa690afb07",
|
||||
"Admin",
|
||||
None,
|
||||
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("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);
|
||||
for (id, name, _created_at_str, _updated_at_str) in roles {
|
||||
let uuid = Uuid::parse_str(id).unwrap_or_else(|_| Uuid::new_v4());
|
||||
|
||||
let existing = RoleEntity::find_by_id(uuid).one(db).await?;
|
||||
if existing.is_some() {
|
||||
println!("ℹ️ Skipping (already exists): {name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
let role_model = RoleBuilder::new()
|
||||
.name(name.to_string())
|
||||
.description("System generated role".to_string())
|
||||
.permissions(vec![])
|
||||
.is_default(false)
|
||||
.build()?;
|
||||
let mut role_model = role_model;
|
||||
role_model.id = Set(uuid);
|
||||
role_model.is_system_role = Set(true);
|
||||
role_model.created_at = Set(Utc::now());
|
||||
role_model.updated_at = Set(Utc::now());
|
||||
|
||||
role_model.insert(db).await?;
|
||||
println!("✅ Inserted role: {name}");
|
||||
}
|
||||
println!("✅ All Roles seeded");
|
||||
Ok(())
|
||||
|
||||
@@ -1,58 +1,109 @@
|
||||
use imphnen_iam::{get_iso_date, make_thing, Env, PermissionsEnum};
|
||||
use imphnen_entities::seaorm::auth::roles::ActiveModel as RoleActiveModel;
|
||||
use imphnen_entities::seaorm::auth::roles::Entity as RolesEntity;
|
||||
use imphnen_iam::PermissionsEnum;
|
||||
use imphnen_libs::postgres::{PostgresConfig, PostgresConnection};
|
||||
use sea_orm::ActiveModelTrait;
|
||||
use sea_orm::ActiveValue::Set;
|
||||
use sea_orm::EntityTrait;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::error::Error;
|
||||
use surrealdb::opt::auth::Root;
|
||||
use surrealdb::engine::any;
|
||||
use imphnen_libs::enviroment::load_env;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
load_env();
|
||||
let env = Env::new();
|
||||
let db = any::connect(&env.surrealdb_url).await?;
|
||||
db.signin(Root {
|
||||
username: &env.surrealdb_username,
|
||||
password: &env.surrealdb_password,
|
||||
})
|
||||
.await?;
|
||||
db.use_ns(env.surrealdb_namespace)
|
||||
.use_db(env.surrealdb_dbname)
|
||||
.await?;
|
||||
let permission_refs_admin: Vec<_> = [
|
||||
let config = PostgresConfig::from_env()?;
|
||||
let pg_conn = PostgresConnection::new(config).await?;
|
||||
let db = &pg_conn.conn;
|
||||
println!(
|
||||
"✅ Index 'user_email_index' defined on table 'users' for column 'email'."
|
||||
);
|
||||
|
||||
let roles_permissions = vec![
|
||||
(
|
||||
"f6b03f25-e416-4893-ac88-caaa690afb07",
|
||||
vec![PermissionsEnum::Administrator],
|
||||
),
|
||||
(
|
||||
"3b9f8c4e-6a2d-4f8a-9a12-2d6f8b3c4e5a",
|
||||
vec![
|
||||
PermissionsEnum::ReadListUsers,
|
||||
PermissionsEnum::ReadDetailUsers,
|
||||
PermissionsEnum::CreateUsers,
|
||||
PermissionsEnum::DeleteUsers,
|
||||
PermissionsEnum::UpdateUsers,
|
||||
PermissionsEnum::ActivateUsers,
|
||||
PermissionsEnum::ReadListRoles,
|
||||
PermissionsEnum::ReadDetailRoles,
|
||||
PermissionsEnum::CreateRoles,
|
||||
PermissionsEnum::DeleteRoles,
|
||||
PermissionsEnum::UpdateRoles,
|
||||
PermissionsEnum::ReadListPermissions,
|
||||
PermissionsEnum::ReadDetailPermissions,
|
||||
PermissionsEnum::CreatePermissions,
|
||||
PermissionsEnum::DeletePermissions,
|
||||
PermissionsEnum::UpdatePermissions,
|
||||
PermissionsEnum::CreateGachaClaims,
|
||||
PermissionsEnum::ReadDetailGachaClaims,
|
||||
PermissionsEnum::ReadOwnMentorProfile,
|
||||
PermissionsEnum::UpdateOwnMentorProfile,
|
||||
PermissionsEnum::ReadOwnMentorStatus,
|
||||
PermissionsEnum::ReadListMentors,
|
||||
PermissionsEnum::ReadDetailMentors,
|
||||
PermissionsEnum::ReadListGachaItems,
|
||||
PermissionsEnum::ReadDetailGachaItems,
|
||||
PermissionsEnum::CreateGachaItems,
|
||||
PermissionsEnum::DeleteGachaItems,
|
||||
PermissionsEnum::UpdateGachaItems,
|
||||
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![
|
||||
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,
|
||||
],
|
||||
),
|
||||
(
|
||||
"60f1aeb7-dad2-4e06-bcb5-be1ba510c906",
|
||||
vec![PermissionsEnum::ActivateUsers],
|
||||
),
|
||||
("6d4fea5d-4a08-4b8a-9782-f2ab2183dcf0", vec![]),
|
||||
];
|
||||
|
||||
for (role_id, permissions) in roles_permissions {
|
||||
let role_uuid = Uuid::parse_str(role_id).unwrap_or_else(|_| Uuid::new_v4());
|
||||
let json_permissions = JsonValue::Array(
|
||||
permissions
|
||||
.iter()
|
||||
.map(|perm| make_thing("app_permissions", perm.id()))
|
||||
.collect();
|
||||
let admin_role_id = "f6b03f25-e416-4893-ac88-caaa690afb07";
|
||||
db.query("UPDATE type::thing('app_roles', $role_id) SET permissions = $permissions, updated_at = $updated_at WHERE is_deleted = false")
|
||||
.bind(("role_id", admin_role_id))
|
||||
.bind(("permissions", permission_refs_admin))
|
||||
.bind(("updated_at", get_iso_date()))
|
||||
.await?;
|
||||
println!("✅ All permissions successfully added to Admin role");
|
||||
.map(|p| JsonValue::String(p.id()))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
if let Some(role_model) = RolesEntity::find_by_id(role_uuid).one(db).await? {
|
||||
let mut am: RoleActiveModel = role_model.into();
|
||||
am.permissions = Set(Some(json_permissions));
|
||||
am.update(db).await?;
|
||||
println!("✅ Permissions updated for role: {role_id}");
|
||||
} else {
|
||||
println!("⚠️ Role with id {role_id} not found, skipping permissions update");
|
||||
}
|
||||
}
|
||||
|
||||
println!("✅ All roles permissions updated!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#![allow(clippy::all)]
|
||||
|
||||
use chrono::Utc;
|
||||
use imphnen_entities::seaorm::auth::mentors::ActiveModel as MentorsActiveModel;
|
||||
use imphnen_entities::seaorm::common::events::ActiveModel as EventsActiveModel;
|
||||
use imphnen_entities::seaorm::common::testimonials::ActiveModel as TestimonialsActiveModel;
|
||||
use imphnen_libs::postgres::{PostgresConfig, PostgresConnection};
|
||||
use sea_orm::ActiveModelTrait;
|
||||
use sea_orm::ActiveValue::Set;
|
||||
use serde_json::json;
|
||||
use std::error::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let config = PostgresConfig::from_env()?;
|
||||
let pg_conn = PostgresConnection::new(config).await?;
|
||||
let db = &pg_conn.conn;
|
||||
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
let mut event_model: EventsActiveModel = Default::default();
|
||||
event_model.id = Set(Uuid::parse_str(&uuid)?);
|
||||
event_model.name = Set("Test Event".to_string());
|
||||
event_model.description = Set("Test event description".to_string());
|
||||
event_model.detail_link = Set("https://example.com/event".to_string());
|
||||
event_model.price = Set(50.0);
|
||||
event_model.is_online = Set(true);
|
||||
event_model.start_date = Set(Utc::now());
|
||||
event_model.end_date = Set(Utc::now() + chrono::Duration::days(1));
|
||||
event_model.location = Set(None);
|
||||
event_model.is_deleted = Set(false);
|
||||
match event_model.insert(db).await {
|
||||
Ok(_) => println!("✅ Inserted test event"),
|
||||
Err(_) => {
|
||||
println!("⚠️ Test event already exists or could not be inserted, skipping")
|
||||
}
|
||||
};
|
||||
|
||||
let mut testimonial_model: TestimonialsActiveModel = Default::default();
|
||||
testimonial_model.id =
|
||||
Set(Uuid::parse_str("00000000-0000-0000-0000-000000000001")?);
|
||||
testimonial_model.user_id =
|
||||
Set(Uuid::parse_str("c3b1d6a8-8d4f-4b36-b789-2e532ec7a7b2")?);
|
||||
testimonial_model.role = Set("Student".to_string());
|
||||
testimonial_model.content = Set("This is a great platform!".to_string());
|
||||
testimonial_model.is_deleted = Set(false);
|
||||
match testimonial_model.insert(db).await {
|
||||
Ok(_) => println!("✅ Inserted test testimonial"),
|
||||
Err(_) => println!(
|
||||
"⚠️ Test testimonial already exists or could not be inserted, skipping"
|
||||
),
|
||||
};
|
||||
|
||||
let mentor_id = Uuid::new_v4();
|
||||
let mut mentor_model: MentorsActiveModel = Default::default();
|
||||
mentor_model.id = Set(mentor_id);
|
||||
mentor_model.user_id =
|
||||
Set(Uuid::parse_str("c3b1d6a8-8d4f-4b36-b789-2e532ec7a7b2")?);
|
||||
mentor_model.industries = Set(Some(json!(["Technology", "Education"])));
|
||||
mentor_model.expertise = Set(Some(json!(["Software Development"])));
|
||||
mentor_model.languages = Set(Some(json!(["English", "Indonesian"])));
|
||||
mentor_model.current_company = Set(Some("Tech Corp".to_string()));
|
||||
mentor_model.current_role = Set(Some("Senior Engineer".to_string()));
|
||||
mentor_model.years_of_experience = Set(Some(5));
|
||||
mentor_model.topics_of_interest = Set(Some(json!(["Rust", "Web Development"])));
|
||||
mentor_model.preferred_mentee_level = Set(Some("Beginner".to_string()));
|
||||
mentor_model.preferred_mentoring_formats = Set(Some(json!(["1:1", "Group"])));
|
||||
mentor_model.availability_commitment = Set(Some("Weekly".to_string()));
|
||||
mentor_model.mentoring_rate = Set(Some(100.0));
|
||||
mentor_model.status = Set(Some("active".to_string()));
|
||||
mentor_model.is_deleted = Set(false);
|
||||
mentor_model.created_at = Set(chrono::Utc::now());
|
||||
mentor_model.updated_at = Set(chrono::Utc::now());
|
||||
mentor_model.insert(db).await?;
|
||||
println!("✅ Inserted test mentor via SeaORM");
|
||||
|
||||
println!("✅ All test data seeded successfully");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,22 +1,20 @@
|
||||
use imphnen_iam::UsersSchema;
|
||||
use imphnen_libs::enviroment::load_env;
|
||||
use imphnen_utils::{get_iso_date, hash_password, Env};
|
||||
#![allow(clippy::all)]
|
||||
|
||||
use imphnen_entities::seaorm::auth::users::ActiveModel as UsersActiveModel;
|
||||
use imphnen_entities::seaorm::auth::users::Entity as UserEntity;
|
||||
use imphnen_libs::hash_password;
|
||||
use imphnen_libs::postgres::{PostgresConfig, PostgresConnection};
|
||||
|
||||
use chrono::Utc;
|
||||
use sea_orm::{ActiveModelTrait, ActiveValue::Set, EntityTrait, IntoActiveModel};
|
||||
use std::error::Error;
|
||||
use surrealdb::{opt::auth::Root, sql::Thing};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
load_env();
|
||||
let env = Env::new();
|
||||
use surrealdb::engine::any;
|
||||
let db = any::connect(&env.surrealdb_url).await?;
|
||||
db.signin(Root {
|
||||
username: &env.surrealdb_username,
|
||||
password: &env.surrealdb_password,
|
||||
})
|
||||
.await?;
|
||||
db.use_ns(env.surrealdb_namespace)
|
||||
.use_db(env.surrealdb_dbname)
|
||||
.await?;
|
||||
let postgres_config = PostgresConfig::from_env()?;
|
||||
let pg_conn = PostgresConnection::new(postgres_config).await?;
|
||||
let db = &pg_conn.conn;
|
||||
|
||||
let users = vec![
|
||||
(
|
||||
@@ -37,32 +35,130 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
"User",
|
||||
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||
),
|
||||
(
|
||||
"665a3cfc-ea5f-4bcd-8769-4a6d8d1451d4",
|
||||
"testuser1@example.com",
|
||||
"Test User 1",
|
||||
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||
),
|
||||
(
|
||||
"3972c139-a450-416c-93b0-c42539dc780f",
|
||||
"testuser2@example.com",
|
||||
"Test User 2",
|
||||
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||
),
|
||||
(
|
||||
"b426c0a9-0efb-4e26-b078-4f18767255f3",
|
||||
"testuser3@example.com",
|
||||
"Test User 3",
|
||||
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||
),
|
||||
(
|
||||
"11111111-1111-1111-1111-111111111111",
|
||||
"user4@example.com",
|
||||
"User Four",
|
||||
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||
),
|
||||
(
|
||||
"22222222-2222-2222-2222-222222222222",
|
||||
"user5@example.com",
|
||||
"User Five",
|
||||
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||
),
|
||||
(
|
||||
"33333333-3333-3333-3333-333333333333",
|
||||
"mentor2@example.com",
|
||||
"Mentor Two",
|
||||
"3b9f8c4e-6a2d-4f8a-9a12-2d6f8b3c4e5a",
|
||||
),
|
||||
(
|
||||
"44444444-4444-4444-4444-444444444444",
|
||||
"staff2@example.com",
|
||||
"Staff Two",
|
||||
"50133429-f4b1-4249-9f97-7b86e6ee9d86",
|
||||
),
|
||||
(
|
||||
"55555555-5555-5555-5555-555555555555",
|
||||
"user6@example.com",
|
||||
"User Six",
|
||||
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||
),
|
||||
(
|
||||
"66666666-6666-6666-6666-666666666666",
|
||||
"user7@example.com",
|
||||
"User Seven",
|
||||
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||
),
|
||||
(
|
||||
"77777777-7777-7777-7777-777777777777",
|
||||
"user8@example.com",
|
||||
"User Eight",
|
||||
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||
),
|
||||
(
|
||||
"88888888-8888-8888-8888-888888888888",
|
||||
"user9@example.com",
|
||||
"User Nine",
|
||||
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||
),
|
||||
(
|
||||
"99999999-9999-9999-9999-999999999999",
|
||||
"user10@example.com",
|
||||
"User Ten",
|
||||
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||
),
|
||||
];
|
||||
|
||||
for (id, email, fullname, role_id) in users {
|
||||
let user = UsersSchema {
|
||||
id: Thing::from(("app_users", id)),
|
||||
fullname: fullname.into(),
|
||||
email: email.into(),
|
||||
password: hash_password("password").unwrap(),
|
||||
avatar: None,
|
||||
phone_number: "081234567890".into(),
|
||||
is_active: true,
|
||||
is_deleted: false,
|
||||
gender: None,
|
||||
birthdate: None,
|
||||
role: Thing::from(("app_roles", role_id)),
|
||||
created_at: get_iso_date(),
|
||||
updated_at: get_iso_date(),
|
||||
for (id, email, fullname, role_id_str) in users {
|
||||
let role_uuid = Some(
|
||||
Uuid::parse_str(role_id_str)
|
||||
.map_err(|e| format!("Invalid UUID for role: {role_id_str} - {e}"))?,
|
||||
);
|
||||
|
||||
let uid = Uuid::parse_str(id)?;
|
||||
|
||||
let names: Vec<&str> = fullname.split_whitespace().collect();
|
||||
let first_name = names.first().map(|s| s.to_string());
|
||||
let last_name = if names.len() > 1 {
|
||||
Some(names[1..].join(" "))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
db.create::<Option<UsersSchema>>(("app_users", id))
|
||||
.content(user)
|
||||
.await?;
|
||||
let password = "password";
|
||||
let hashed = hash_password(password).unwrap();
|
||||
|
||||
println!("✅ Inserted user: {} ({})", fullname, email);
|
||||
let existing_user = UserEntity::find_by_id(uid).one(db).await?;
|
||||
let is_update = existing_user.is_some();
|
||||
|
||||
let mut user_model: UsersActiveModel = if let Some(existing) = existing_user {
|
||||
println!("🔄 Updating user: {fullname} ({email})");
|
||||
existing.into_active_model()
|
||||
} else {
|
||||
println!("✅ Inserting user: {fullname} ({email})");
|
||||
let mut active: UsersActiveModel = Default::default();
|
||||
active.id = Set(uid);
|
||||
active.created_at = Set(Utc::now());
|
||||
active
|
||||
};
|
||||
|
||||
user_model.email = Set(email.to_string());
|
||||
user_model.password_hash = Set(hashed);
|
||||
user_model.username = Set(email.to_string());
|
||||
user_model.first_name = Set(first_name);
|
||||
user_model.last_name = Set(last_name);
|
||||
user_model.avatar_url = Set(Some("https://example.com/avatar.jpg".to_string()));
|
||||
user_model.is_verified = Set(true);
|
||||
user_model.is_active = Set(true);
|
||||
user_model.role_id = Set(role_uuid);
|
||||
user_model.updated_at = Set(Utc::now());
|
||||
|
||||
if is_update {
|
||||
user_model.update(db).await?;
|
||||
} else {
|
||||
user_model.insert(db).await?;
|
||||
}
|
||||
}
|
||||
|
||||
println!("✅ All Users seeded");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
#![allow(clippy::all)]
|
||||
|
||||
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()?;
|
||||
#[cfg(target_os = "windows")]
|
||||
let status = Command::new(format!("./target/release/{}.exe", bin)).status()?;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let status = Command::new(format!("./target/release/{}", bin)).status()?;
|
||||
|
||||
if !status.success() {
|
||||
Err(format!("❌ Failed to run seed: {bin}").into())
|
||||
@@ -20,7 +25,9 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
run_seed("seed_roles_permissions")?;
|
||||
run_seed("seed_users")?;
|
||||
run_seed("seed_events")?;
|
||||
|
||||
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,395 @@
|
||||
use chrono::Utc;
|
||||
use imphnen_entities::seaorm::auth::roles::{
|
||||
Entity as RolesEntity, Model as RoleModel,
|
||||
};
|
||||
use imphnen_entities::seaorm::auth::users::{
|
||||
Entity as UsersEntity, Model as UserModel,
|
||||
};
|
||||
use imphnen_libs::postgres::{PostgresConfig, PostgresConnection, PostgresError};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, DbErr, EntityTrait, PaginatorTrait, Set, TransactionTrait,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("🚀 Starting PostgreSQL Connection Test");
|
||||
println!("=====================================");
|
||||
|
||||
let config = PostgresConfig::from_env()?;
|
||||
println!("✅ Configuration loaded successfully");
|
||||
println!(
|
||||
" Database URL: {}",
|
||||
config
|
||||
.database_url
|
||||
.replace("postgres://", "postgres://****:****@")
|
||||
);
|
||||
println!(" Pool size: {}", config.pool_size);
|
||||
println!(" Connect timeout: {}s", config.connect_timeout);
|
||||
println!(" Retry attempts: {}", config.retry_attempts);
|
||||
|
||||
println!("\n🔌 Testing PostgreSQL connection...");
|
||||
match test_connection(config).await {
|
||||
Ok(()) => {
|
||||
println!("✅ All PostgreSQL tests passed successfully!");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
println!("❌ PostgreSQL test failed: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_connection(config: PostgresConfig) -> Result<(), PostgresError> {
|
||||
println!(" Creating PostgreSQL connection...");
|
||||
let postgres_conn = PostgresConnection::new(config).await?;
|
||||
let connection = Arc::new(postgres_conn);
|
||||
println!(" ✅ Connection established successfully");
|
||||
|
||||
println!(" Testing basic connectivity...");
|
||||
test_basic_connectivity(&connection).await?;
|
||||
println!(" ✅ Basic connectivity test passed");
|
||||
|
||||
println!(" Testing table existence...");
|
||||
test_table_existence(&connection).await?;
|
||||
println!(" ✅ Table existence test passed");
|
||||
|
||||
println!(" Testing CRUD operations...");
|
||||
test_crud_operations(&connection).await?;
|
||||
println!(" ✅ CRUD operations test passed");
|
||||
|
||||
println!(" Testing transaction support...");
|
||||
test_transactions(&connection).await?;
|
||||
println!(" ✅ Transaction support test passed");
|
||||
|
||||
println!(" Testing error handling...");
|
||||
test_error_handling(&connection).await?;
|
||||
println!(" ✅ Error handling test passed");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn test_basic_connectivity(
|
||||
connection: &Arc<PostgresConnection>,
|
||||
) -> Result<(), PostgresError> {
|
||||
let statement = sea_orm::Statement::from_string(
|
||||
connection.get_database_backend(),
|
||||
"SELECT 1 as test_value, current_timestamp as current_time".to_string(),
|
||||
);
|
||||
|
||||
let result = connection.query_one(statement).await?.ok_or_else(|| {
|
||||
PostgresError::ConnectionError(sea_orm::DbErr::Custom(
|
||||
"No results returned".to_string(),
|
||||
))
|
||||
})?;
|
||||
|
||||
let test_value: Option<i32> = result.try_get("", "test_value").ok();
|
||||
let current_time: Option<String> = result.try_get("", "current_time").ok();
|
||||
|
||||
if test_value != Some(1) {
|
||||
return Err(PostgresError::ConnectionError(sea_orm::DbErr::Custom(
|
||||
format!("Expected test_value=1, got {:?}", test_value),
|
||||
)));
|
||||
}
|
||||
|
||||
if current_time.is_none() {
|
||||
return Err(PostgresError::ConnectionError(sea_orm::DbErr::Custom(
|
||||
"Expected current_time to be set".to_string(),
|
||||
)));
|
||||
}
|
||||
|
||||
println!(
|
||||
" 📝 Query result: test_value={:?}, current_time={:?}",
|
||||
test_value, current_time
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn test_table_existence(
|
||||
connection: &Arc<PostgresConnection>,
|
||||
) -> Result<(), PostgresError> {
|
||||
use sea_orm::EntityTrait;
|
||||
|
||||
println!(" 📋 Checking users table...");
|
||||
let user_count = UsersEntity::find()
|
||||
.count(&connection.conn)
|
||||
.await
|
||||
.map_err(PostgresError::ConnectionError)?;
|
||||
println!(
|
||||
" 📊 Users table accessible, current count: {}",
|
||||
user_count
|
||||
);
|
||||
|
||||
println!(" 📋 Checking roles table...");
|
||||
let role_count = RolesEntity::find()
|
||||
.count(&connection.conn)
|
||||
.await
|
||||
.map_err(PostgresError::ConnectionError)?;
|
||||
println!(
|
||||
" 📊 Roles table accessible, current count: {}",
|
||||
role_count
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn test_crud_operations(
|
||||
connection: &Arc<PostgresConnection>,
|
||||
) -> Result<(), PostgresError> {
|
||||
use sea_orm::{ActiveModelTrait, Set};
|
||||
|
||||
println!(" ➕ Creating test user...");
|
||||
let test_user_id = Uuid::new_v4();
|
||||
let now = Utc::now();
|
||||
|
||||
let user_model = imphnen_entities::seaorm::auth::users::ActiveModel {
|
||||
id: Set(test_user_id),
|
||||
email: Set(format!("test_user_{}@example.com", test_user_id)),
|
||||
password_hash: Set("test_password_hash".to_string()),
|
||||
username: Set(format!("testuser_{}", test_user_id)),
|
||||
first_name: Set(Some("Test".to_string())),
|
||||
last_name: Set(Some("User".to_string())),
|
||||
avatar_url: Set(None),
|
||||
is_verified: Set(false),
|
||||
is_active: Set(true),
|
||||
metadata: Set(None),
|
||||
role_id: Set(None),
|
||||
created_at: Set(now),
|
||||
updated_at: Set(now),
|
||||
deleted_at: Set(None),
|
||||
};
|
||||
|
||||
let created_user = user_model
|
||||
.insert(&connection.conn)
|
||||
.await
|
||||
.map_err(PostgresError::ConnectionError)?;
|
||||
|
||||
println!(" ✅ Created user with ID: {}", created_user.id);
|
||||
|
||||
println!(" 🔍 Reading test user...");
|
||||
let found_user = UsersEntity::find_by_id(test_user_id)
|
||||
.one(&connection.conn)
|
||||
.await
|
||||
.map_err(PostgresError::ConnectionError)?
|
||||
.ok_or_else(|| {
|
||||
PostgresError::ConnectionError(sea_orm::DbErr::Custom(
|
||||
"User not found after creation".to_string(),
|
||||
))
|
||||
})?;
|
||||
|
||||
println!(
|
||||
" ✅ Found user: {} ({})",
|
||||
found_user.username, found_user.email
|
||||
);
|
||||
|
||||
println!(" ✏️ Updating test user...");
|
||||
let mut update_model: imphnen_entities::seaorm::auth::users::ActiveModel =
|
||||
found_user.into();
|
||||
update_model.first_name = Set(Some("Updated".to_string()));
|
||||
update_model.updated_at = Set(Utc::now());
|
||||
|
||||
let updated_user = update_model
|
||||
.update(&connection.conn)
|
||||
.await
|
||||
.map_err(PostgresError::ConnectionError)?;
|
||||
|
||||
println!(
|
||||
" ✅ Updated user first name to: {:?}",
|
||||
updated_user.first_name
|
||||
);
|
||||
|
||||
println!(" 🗑️ Deleting test user...");
|
||||
UsersEntity::delete_by_id(updated_user.id)
|
||||
.exec(&connection.conn)
|
||||
.await
|
||||
.map_err(PostgresError::ConnectionError)?;
|
||||
|
||||
println!(" ✅ Test user deleted successfully");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn test_transactions(
|
||||
connection: &Arc<PostgresConnection>,
|
||||
) -> Result<(), PostgresError> {
|
||||
println!(" 💰 Testing transaction support...");
|
||||
|
||||
let transaction_result = connection
|
||||
.conn
|
||||
.transaction(|txn| {
|
||||
Box::pin(async move {
|
||||
let test_user_id = Uuid::new_v4();
|
||||
let now = Utc::now();
|
||||
|
||||
let user_model = imphnen_entities::seaorm::auth::users::ActiveModel {
|
||||
id: Set(test_user_id),
|
||||
email: Set(format!("transaction_test_{}@example.com", test_user_id)),
|
||||
password_hash: Set("transaction_password_hash".to_string()),
|
||||
username: Set(format!("transaction_user_{}", test_user_id)),
|
||||
first_name: Set(Some("Transaction".to_string())),
|
||||
last_name: Set(Some("Test".to_string())),
|
||||
avatar_url: Set(None),
|
||||
is_verified: Set(false),
|
||||
is_active: Set(true),
|
||||
metadata: Set(None),
|
||||
role_id: Set(None),
|
||||
created_at: Set(now),
|
||||
updated_at: Set(now),
|
||||
deleted_at: Set(None),
|
||||
};
|
||||
|
||||
let _created_user = user_model.insert(txn).await?;
|
||||
|
||||
Err::<(), DbErr>(DbErr::Custom("Simulated transaction failure".to_string()))
|
||||
})
|
||||
})
|
||||
.await;
|
||||
|
||||
match transaction_result {
|
||||
Err(e) => {
|
||||
let e_text = format!("{:?}", e);
|
||||
if e_text.contains("Simulated transaction failure") {
|
||||
println!(" ✅ Transaction failed as expected, rollback successful");
|
||||
} else {
|
||||
return Err(PostgresError::OperationFailed(format!(
|
||||
"Unexpected transaction result: {}",
|
||||
e_text
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(_) => {
|
||||
return Err(PostgresError::OperationFailed(
|
||||
"Unexpected transaction result: transaction unexpectedly succeeded"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let user_exists = UsersEntity::find_by_id(Uuid::nil())
|
||||
.one(&connection.conn)
|
||||
.await
|
||||
.map_err(PostgresError::ConnectionError)?
|
||||
.is_some();
|
||||
|
||||
if user_exists {
|
||||
println!(" ⚠️ User found despite rollback - this might indicate an issue");
|
||||
} else {
|
||||
println!(" ✅ Transaction rollback verified - user not found");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn test_error_handling(
|
||||
connection: &Arc<PostgresConnection>,
|
||||
) -> Result<(), PostgresError> {
|
||||
println!(" ⚠️ Testing error handling...");
|
||||
|
||||
println!(" 🔍 Testing invalid UUID handling...");
|
||||
let invalid_uuid = Uuid::nil();
|
||||
|
||||
match UsersEntity::find_by_id(invalid_uuid)
|
||||
.one(&connection.conn)
|
||||
.await
|
||||
.map_err(PostgresError::ConnectionError)?
|
||||
{
|
||||
Some(_) => {
|
||||
println!(" ✅ Found user with nil UUID (expected in some cases)")
|
||||
}
|
||||
None => println!(" ✅ No user found with nil UUID (expected)"),
|
||||
}
|
||||
|
||||
println!(" 🔍 Testing invalid query handling...");
|
||||
let invalid_statement = sea_orm::Statement::from_string(
|
||||
connection.get_database_backend(),
|
||||
"SELECT * FROM non_existent_table".to_string(),
|
||||
);
|
||||
|
||||
match connection.execute(invalid_statement).await {
|
||||
Err(_) => println!(" ✅ Invalid query properly handled with error"),
|
||||
Ok(_) => println!(" ⚠️ Invalid query unexpectedly succeeded"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub mod test_utils {
|
||||
use super::*;
|
||||
|
||||
pub fn create_test_config() -> PostgresConfig {
|
||||
PostgresConfig {
|
||||
database_url: "postgres://postgres:postgres@localhost:5432/imphnen_test"
|
||||
.to_string(),
|
||||
pool_size: 5,
|
||||
connect_timeout: 10,
|
||||
idle_timeout: 30,
|
||||
max_lifetime: Some(600),
|
||||
retry_attempts: 2,
|
||||
retry_delay: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_test_user_model() -> UserModel {
|
||||
UserModel {
|
||||
id: Uuid::new_v4(),
|
||||
email: format!("test_{}@example.com", Uuid::new_v4()),
|
||||
password_hash: "test_password_hash".to_string(),
|
||||
username: format!("testuser_{}", Uuid::new_v4()),
|
||||
first_name: Some("Test".to_string()),
|
||||
last_name: Some("User".to_string()),
|
||||
avatar_url: None,
|
||||
is_verified: false,
|
||||
is_active: true,
|
||||
metadata: None,
|
||||
role_id: None,
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
deleted_at: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_test_role_model() -> RoleModel {
|
||||
RoleModel {
|
||||
id: Uuid::new_v4(),
|
||||
name: format!("test_role_{}", Uuid::new_v4()),
|
||||
description: "Test role description".to_string(),
|
||||
permissions: Some(serde_json::json!(["test.permission"])),
|
||||
is_system_role: false,
|
||||
is_default: false,
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
deleted_at: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_create_test_config() {
|
||||
let config = test_utils::create_test_config();
|
||||
assert_eq!(config.pool_size, 5);
|
||||
assert_eq!(config.connect_timeout, 10);
|
||||
assert!(config.database_url.contains("imphnen_test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_test_user_model() {
|
||||
let user = test_utils::create_test_user_model();
|
||||
assert!(!user.email.is_empty());
|
||||
assert!(!user.username.is_empty());
|
||||
assert!(user.is_active);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_test_role_model() {
|
||||
let role = test_utils::create_test_role_model();
|
||||
assert!(!role.name.is_empty());
|
||||
assert!(role.permissions.is_some());
|
||||
assert!(!role.is_system_role);
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@ use imphnen_libs::axum_init;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
axum_init(|surrealdb_ws, surrealdb_mem| async {
|
||||
gateway_service(surrealdb_ws, surrealdb_mem).await
|
||||
})
|
||||
.await;
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let _ =
|
||||
axum_init(|postgres_conn| async { gateway_service(postgres_conn).await }).await;
|
||||
}
|
||||
|
||||
+23
-7
@@ -1,25 +1,41 @@
|
||||
[package]
|
||||
name = "imphnen-cms"
|
||||
version = "0.1.0"
|
||||
version = "0.3.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
imphnen-iam = { version = "0.1.0", path = "../imphnen-iam" }
|
||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||
async-trait.workspace = true
|
||||
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
|
||||
zod-rs.workspace = true
|
||||
zod-rs-util.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
|
||||
sea-orm.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
paginator-rs.workspace = true
|
||||
paginator-utils.workspace = true
|
||||
paginator-sea-orm.workspace = true
|
||||
paginator-axum.workspace = true
|
||||
sqlx.workspace = true
|
||||
image.workspace = true
|
||||
qrcode.workspace = true
|
||||
|
||||
[package.metadata.validator.regex]
|
||||
VALID_URL_REGEX = "^https?://"
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
use crate::events::domain::{EventEntity, EventRepository, EventService};
|
||||
use async_trait::async_trait;
|
||||
use imphnen_utils::AppError;
|
||||
use paginator_rs::PaginationParams;
|
||||
use paginator_utils::PaginatorResponse;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct EventServiceImpl {
|
||||
repo: Arc<dyn EventRepository>,
|
||||
}
|
||||
|
||||
impl EventServiceImpl {
|
||||
pub fn new(repo: Arc<dyn EventRepository>) -> Self {
|
||||
Self { repo }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EventService for EventServiceImpl {
|
||||
async fn list(
|
||||
&self,
|
||||
params: PaginationParams,
|
||||
) -> Result<PaginatorResponse<EventEntity>, AppError> {
|
||||
self.repo.find_all(params).await
|
||||
}
|
||||
|
||||
async fn get(&self, id: Uuid) -> Result<EventEntity, AppError> {
|
||||
self.repo.find_by_id(id).await
|
||||
}
|
||||
|
||||
async fn create(&self, entity: EventEntity) -> Result<(), AppError> {
|
||||
self.repo.create(entity).await
|
||||
}
|
||||
|
||||
async fn update(&self, entity: EventEntity) -> Result<(), AppError> {
|
||||
self.repo.update(entity).await
|
||||
}
|
||||
|
||||
async fn delete(&self, id: Uuid) -> Result<(), AppError> {
|
||||
self.repo.delete(id).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod event_service;
|
||||
|
||||
pub use event_service::EventServiceImpl;
|
||||
@@ -0,0 +1,18 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EventEntity {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub detail_link: String,
|
||||
pub price: f64,
|
||||
pub is_online: bool,
|
||||
pub is_deleted: bool,
|
||||
pub location: Option<String>,
|
||||
pub start_date: DateTime<Utc>,
|
||||
pub end_date: DateTime<Utc>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
pub mod event;
|
||||
pub mod repository;
|
||||
pub mod service;
|
||||
|
||||
pub use event::EventEntity;
|
||||
pub use repository::EventRepository;
|
||||
pub use service::EventService;
|
||||
@@ -0,0 +1,18 @@
|
||||
use super::event::EventEntity;
|
||||
use async_trait::async_trait;
|
||||
use imphnen_utils::AppError;
|
||||
use paginator_rs::PaginationParams;
|
||||
use paginator_utils::PaginatorResponse;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[async_trait]
|
||||
pub trait EventRepository: Send + Sync {
|
||||
async fn find_all(
|
||||
&self,
|
||||
params: PaginationParams,
|
||||
) -> Result<PaginatorResponse<EventEntity>, AppError>;
|
||||
async fn find_by_id(&self, id: Uuid) -> Result<EventEntity, AppError>;
|
||||
async fn create(&self, entity: EventEntity) -> Result<(), AppError>;
|
||||
async fn update(&self, entity: EventEntity) -> Result<(), AppError>;
|
||||
async fn delete(&self, id: Uuid) -> Result<(), AppError>;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
use super::event::EventEntity;
|
||||
use async_trait::async_trait;
|
||||
use imphnen_utils::AppError;
|
||||
use paginator_rs::PaginationParams;
|
||||
use paginator_utils::PaginatorResponse;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[async_trait]
|
||||
pub trait EventService: Send + Sync {
|
||||
async fn list(
|
||||
&self,
|
||||
params: PaginationParams,
|
||||
) -> Result<PaginatorResponse<EventEntity>, AppError>;
|
||||
async fn get(&self, id: Uuid) -> Result<EventEntity, AppError>;
|
||||
async fn create(&self, entity: EventEntity) -> Result<(), AppError>;
|
||||
async fn update(&self, entity: EventEntity) -> Result<(), AppError>;
|
||||
async fn delete(&self, id: Uuid) -> Result<(), AppError>;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
use crate::events::domain::event::EventEntity;
|
||||
use chrono::{DateTime, Utc};
|
||||
use imphnen_libs::ZodValidate;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct EventsCreateRequestDto {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub detail_link: String,
|
||||
pub price: f64,
|
||||
#[schema(example = "2025-09-20T13:00:00Z", value_type = String)]
|
||||
pub end_date: DateTime<Utc>,
|
||||
#[schema(example = "2025-09-20T13:00:00Z", value_type = String)]
|
||||
pub start_date: DateTime<Utc>,
|
||||
pub location: Option<String>,
|
||||
pub is_online: bool,
|
||||
}
|
||||
|
||||
impl ZodValidate for EventsCreateRequestDto {
|
||||
fn zod_validate(value: &serde_json::Value) -> Result<Self, String> {
|
||||
serde_json::from_value(value.clone()).map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EventsCreateRequestDto> for EventEntity {
|
||||
fn from(dto: EventsCreateRequestDto) -> Self {
|
||||
EventEntity {
|
||||
id: Uuid::new_v4(),
|
||||
name: dto.name,
|
||||
description: dto.description,
|
||||
detail_link: dto.detail_link,
|
||||
price: dto.price,
|
||||
is_online: dto.is_online,
|
||||
is_deleted: false,
|
||||
location: dto.location,
|
||||
start_date: dto.start_date,
|
||||
end_date: dto.end_date,
|
||||
created_at: chrono::Utc::now(),
|
||||
updated_at: chrono::Utc::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct EventsUpdateRequestDto {
|
||||
pub name: String,
|
||||
#[schema(example = "2025-09-20T13:00:00Z", value_type = String)]
|
||||
pub end_date: DateTime<Utc>,
|
||||
#[schema(example = "2025-09-20T13:00:00Z", value_type = String)]
|
||||
pub start_date: DateTime<Utc>,
|
||||
pub price: f64,
|
||||
pub is_online: bool,
|
||||
pub description: String,
|
||||
pub detail_link: String,
|
||||
pub location: Option<String>,
|
||||
}
|
||||
|
||||
impl ZodValidate for EventsUpdateRequestDto {
|
||||
fn zod_validate(value: &serde_json::Value) -> Result<Self, String> {
|
||||
serde_json::from_value(value.clone()).map_err(|e| e.to_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,
|
||||
}
|
||||
|
||||
impl From<EventEntity> for EventsListItemDto {
|
||||
fn from(e: EventEntity) -> Self {
|
||||
EventsListItemDto {
|
||||
id: e.id.to_string(),
|
||||
name: e.name,
|
||||
description: e.description,
|
||||
detail_link: e.detail_link,
|
||||
price: e.price,
|
||||
is_online: e.is_online,
|
||||
start_date: e.start_date.to_rfc3339(),
|
||||
end_date: e.end_date.to_rfc3339(),
|
||||
created_at: e.created_at.to_rfc3339(),
|
||||
location: e.location,
|
||||
is_deleted: e.is_deleted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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>,
|
||||
}
|
||||
|
||||
impl From<EventEntity> for EventsDetailItemDto {
|
||||
fn from(e: EventEntity) -> Self {
|
||||
EventsDetailItemDto {
|
||||
id: e.id.to_string(),
|
||||
name: e.name,
|
||||
description: e.description,
|
||||
detail_link: e.detail_link,
|
||||
price: e.price,
|
||||
is_online: e.is_online,
|
||||
start_date: e.start_date.to_rfc3339(),
|
||||
end_date: e.end_date.to_rfc3339(),
|
||||
created_at: e.created_at.to_rfc3339(),
|
||||
updated_at: e.updated_at.to_rfc3339(),
|
||||
location: e.location,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
use super::dto::{
|
||||
EventsCreateRequestDto, EventsDetailItemDto, EventsListItemDto,
|
||||
EventsUpdateRequestDto,
|
||||
};
|
||||
use crate::events::domain::EventService;
|
||||
use axum::{
|
||||
Extension,
|
||||
extract::Path,
|
||||
http::HeaderMap,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use imphnen_entities::ResponseSuccessDto;
|
||||
use imphnen_iam::{PermissionsEnum, require_permissions};
|
||||
use imphnen_libs::{AppState, ValidatedJson};
|
||||
use imphnen_utils::AppError;
|
||||
use imphnen_utils::{ApiMessage, ApiPaginated, ApiSuccess};
|
||||
use paginator_axum::PaginationQuery;
|
||||
use paginator_utils::PaginatorResponse;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v1/landing/cms/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"),
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "[PUBLIC] Get event list")
|
||||
),
|
||||
tag = "Events"
|
||||
)]
|
||||
pub async fn get_event_list(
|
||||
Extension(service): Extension<Arc<dyn EventService>>,
|
||||
PaginationQuery(params): PaginationQuery,
|
||||
) -> Response {
|
||||
match service.list(params).await {
|
||||
Ok(result) => {
|
||||
let mapped = PaginatorResponse {
|
||||
data: result
|
||||
.data
|
||||
.into_iter()
|
||||
.map(EventsListItemDto::from)
|
||||
.collect::<Vec<_>>(),
|
||||
meta: result.meta,
|
||||
};
|
||||
ApiPaginated(mapped).into_response()
|
||||
}
|
||||
Err(e) => ApiMessage::new(axum::http::StatusCode::BAD_REQUEST, e.to_string())
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v1/landing/cms/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(service): Extension<Arc<dyn EventService>>,
|
||||
Path(id): Path<String>,
|
||||
) -> Response {
|
||||
let uuid = match Uuid::parse_str(&id) {
|
||||
Ok(u) => u,
|
||||
Err(e) => {
|
||||
return ApiMessage::new(
|
||||
axum::http::StatusCode::BAD_REQUEST,
|
||||
format!("Invalid UUID: {e}"),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
match service.get(uuid).await {
|
||||
Ok(event) => ApiSuccess(EventsDetailItemDto::from(event)).into_response(),
|
||||
Err(e) => ApiMessage::new(axum::http::StatusCode::NOT_FOUND, e.to_string())
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
security(("Bearer" = [])),
|
||||
path = "/v1/landing/cms/events/create",
|
||||
request_body = EventsCreateRequestDto,
|
||||
responses(
|
||||
(status = 201, description = "[ADMIN] Create new event")
|
||||
),
|
||||
tag = "Events"
|
||||
)]
|
||||
pub async fn post_create_event(
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
Extension(service): Extension<Arc<dyn EventService>>,
|
||||
ValidatedJson(payload): ValidatedJson<EventsCreateRequestDto>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
require_permissions!(headers, state, [PermissionsEnum::Administrator], {
|
||||
let entity = payload.into();
|
||||
service.create(entity).await?;
|
||||
Ok(ApiMessage::created("Event created"))
|
||||
})
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
security(("Bearer" = [])),
|
||||
path = "/v1/landing/cms/events/update/{id}",
|
||||
params(
|
||||
("id" = String, Path, description = "Event ID")
|
||||
),
|
||||
request_body = EventsUpdateRequestDto,
|
||||
responses(
|
||||
(status = 200, description = "[ADMIN] Update event")
|
||||
),
|
||||
tag = "Events"
|
||||
)]
|
||||
pub async fn patch_update_event(
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
Extension(service): Extension<Arc<dyn EventService>>,
|
||||
Path(id): Path<String>,
|
||||
ValidatedJson(payload): ValidatedJson<EventsUpdateRequestDto>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
require_permissions!(headers, state, [PermissionsEnum::Administrator], {
|
||||
let uuid = Uuid::parse_str(&id)
|
||||
.map_err(|e| AppError::BadRequestError(format!("Invalid UUID: {e}")))?;
|
||||
let existing = service.get(uuid).await?;
|
||||
let entity = crate::events::domain::EventEntity {
|
||||
id: existing.id,
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
detail_link: payload.detail_link,
|
||||
price: payload.price,
|
||||
is_online: payload.is_online,
|
||||
location: payload.location,
|
||||
start_date: payload.start_date,
|
||||
end_date: payload.end_date,
|
||||
is_deleted: existing.is_deleted,
|
||||
created_at: existing.created_at,
|
||||
updated_at: chrono::Utc::now(),
|
||||
};
|
||||
service.update(entity).await?;
|
||||
Ok(ApiMessage::ok("Event updated"))
|
||||
})
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
security(("Bearer" = [])),
|
||||
path = "/v1/landing/cms/events/delete/{id}",
|
||||
params(
|
||||
("id" = String, Path, description = "Event ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "[ADMIN] Soft delete event")
|
||||
),
|
||||
tag = "Events"
|
||||
)]
|
||||
pub async fn delete_event(
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
Extension(service): Extension<Arc<dyn EventService>>,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
require_permissions!(headers, state, [PermissionsEnum::Administrator], {
|
||||
let uuid = Uuid::parse_str(&id)
|
||||
.map_err(|e| AppError::BadRequestError(format!("Invalid UUID: {e}")))?;
|
||||
service.delete(uuid).await?;
|
||||
Ok(ApiMessage::ok("Event deleted"))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod dto;
|
||||
pub mod handlers;
|
||||
pub mod routes;
|
||||
|
||||
pub use routes::{events_protected_routes, events_public_routes};
|
||||
@@ -0,0 +1,35 @@
|
||||
use super::handlers::{
|
||||
delete_event, get_event_by_id, get_event_list, patch_update_event,
|
||||
post_create_event,
|
||||
};
|
||||
use crate::events::application::EventServiceImpl;
|
||||
use crate::events::domain::EventService;
|
||||
use crate::events::infrastructure::persistence::PostgresEventRepository;
|
||||
use axum::{
|
||||
Extension, Router,
|
||||
routing::{delete, get, patch, post},
|
||||
};
|
||||
use sea_orm::DatabaseConnection;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn build_service(db: DatabaseConnection) -> Arc<dyn EventService> {
|
||||
let repo = Arc::new(PostgresEventRepository::new(db));
|
||||
Arc::new(EventServiceImpl::new(repo))
|
||||
}
|
||||
|
||||
pub fn events_public_routes(db: DatabaseConnection) -> Router {
|
||||
let service = build_service(db);
|
||||
Router::new()
|
||||
.route("/events", get(get_event_list))
|
||||
.route("/events/detail/{id}", get(get_event_by_id))
|
||||
.layer(Extension(service))
|
||||
}
|
||||
|
||||
pub fn events_protected_routes(db: DatabaseConnection) -> Router {
|
||||
let service = build_service(db);
|
||||
Router::new()
|
||||
.route("/events/create", post(post_create_event))
|
||||
.route("/events/update/{id}", patch(patch_update_event))
|
||||
.route("/events/delete/{id}", delete(delete_event))
|
||||
.layer(Extension(service))
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod http;
|
||||
pub mod persistence;
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod postgres_event_repository;
|
||||
|
||||
pub use postgres_event_repository::PostgresEventRepository;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user