Compare commits
147 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f49457dc5b | ||
|
|
d9e2e247ea | ||
|
|
c6f2f2f150 | ||
|
|
56b9c66cb8 | ||
|
|
5d8a0d0e1f | ||
|
|
f4cb9beda5 | ||
|
|
b66fc3c06d | ||
|
|
ab2e1a9236 | ||
|
|
ab77b400a1 | ||
|
|
eb7ab63563 | ||
|
|
4cf7d01622 | ||
|
|
a876078a9c | ||
|
|
495f2ae3c6 | ||
|
|
4e6d11baf0 | ||
|
|
a951b982b3 | ||
|
|
a33be66504 | ||
|
|
f41b9d5887 | ||
|
|
3c608463e6 | ||
|
|
eeffbe124a | ||
|
|
d7f2d54faa | ||
|
|
7db4b03634 | ||
|
|
77760a681a | ||
|
|
f9c1447ceb | ||
|
|
fb749c1902 | ||
|
|
240c44aa07 | ||
|
|
92cd8642c8 | ||
|
|
89d90cf919 | ||
|
|
920c6b6d8b | ||
|
|
1dd4df3a1c | ||
|
|
c7d44cc253 | ||
|
|
5082ab1893 | ||
|
|
e8b2425222 | ||
|
|
09d12cefd8 | ||
|
|
5f1166965d | ||
|
|
0dbab182e9 | ||
|
|
512f3f99fd | ||
|
|
6fb4fad705 | ||
|
|
fa92529e9b | ||
|
|
d6c6051a6c | ||
|
|
ce063bd3ac | ||
|
|
4468894dfb | ||
|
|
8b00b919ad | ||
|
|
64f4a6dfac | ||
|
|
6faa5153b6 | ||
|
|
a771b1e9b0 | ||
|
|
7750f9c54d | ||
|
|
b2d24ee67b | ||
|
|
589a2a5123 | ||
|
|
184d3d357d | ||
|
|
50b3d85270 | ||
|
|
09fdd34ba3 | ||
|
|
bba10261c5 | ||
|
|
46bfe54097 | ||
|
|
503e7a307e | ||
|
|
821b0a6faf | ||
|
|
d60fdff179 | ||
|
|
fdd841e82a | ||
|
|
2d6f0a116a | ||
|
|
bd13fe4ef4 | ||
|
|
6e1b208464 | ||
|
|
76433a409e | ||
|
|
9b4fa679c2 | ||
|
|
c2ae95c4cc | ||
|
|
b2b7f60fd5 | ||
|
|
a465888b31 | ||
|
|
d368bdc84c | ||
|
|
cdc1150505 | ||
|
|
32d525c53c | ||
|
|
a89b40c607 | ||
|
|
b6bd9150d9 | ||
|
|
96e3e3bc86 | ||
|
|
41377f41bb | ||
|
|
fb744f81e2 | ||
|
|
750c3bcbcd | ||
|
|
d4015d7284 | ||
|
|
a9bf3fda73 | ||
|
|
7f467a4814 | ||
|
|
9f10b5e983 | ||
|
|
5291270e6a | ||
|
|
56bba381d8 | ||
|
|
2ff276b162 | ||
|
|
d77191ce0f | ||
|
|
f99803aef4 | ||
|
|
e09fa17013 | ||
|
|
c7e69aa8a5 | ||
|
|
9548068283 | ||
|
|
09958c78f3 | ||
|
|
aced098982 | ||
|
|
7862a34760 | ||
|
|
1384d28cac | ||
|
|
24b7338153 | ||
|
|
30d254eaef | ||
|
|
bb8a936ade | ||
|
|
61044fd30b | ||
|
|
22a4546d0f | ||
|
|
07450416ed | ||
|
|
0d6db0d2a1 | ||
|
|
ab30b3407b | ||
|
|
7a4c735803 | ||
|
|
654c764019 | ||
|
|
7101139250 | ||
|
|
793614841a | ||
|
|
94f2ac5b2a | ||
|
|
d4d39eecaa | ||
|
|
8af01c859c | ||
|
|
3af92d03e8 | ||
|
|
f17d891302 | ||
|
|
51623436b0 | ||
|
|
1b7c7eef8f | ||
|
|
15e4c7e522 | ||
|
|
120ab132a9 | ||
|
|
0b52e8faa8 | ||
|
|
60eaaf390a | ||
|
|
9101badb1c | ||
|
|
7082111b34 | ||
|
|
a156f2681e | ||
|
|
7cb823c957 | ||
|
|
817f612224 | ||
|
|
1cbaf9d6bc | ||
|
|
d353f9837f | ||
|
|
14beef72fc | ||
|
|
91a33fe7f6 | ||
|
|
7ad7a0ff41 | ||
|
|
d646469f71 | ||
|
|
4f616ffff1 | ||
|
|
64400fba61 | ||
|
|
fc15e8c63d | ||
|
|
cbba1e5317 | ||
|
|
75b997dcc4 | ||
|
|
853063c59b | ||
|
|
5c65edb8fa | ||
|
|
7707cc116f | ||
|
|
737fe749de | ||
|
|
b9c6f17e3f | ||
|
|
af6a340003 | ||
|
|
8ba3bee944 | ||
|
|
153c3566b6 | ||
|
|
97a4753f4f | ||
|
|
74c3899b55 | ||
|
|
273ac6d1a8 | ||
|
|
5edbb39a63 | ||
|
|
0c974c4113 | ||
|
|
46657a525d | ||
|
|
b36aa6f917 | ||
|
|
cddb0ebef9 | ||
|
|
788c4e3531 | ||
|
|
47f9ad8274 |
@@ -1,8 +1,11 @@
|
||||
# Ignore Docker Compose configuration files
|
||||
docker-compose.yaml
|
||||
docker-compose-dev.yaml
|
||||
|
||||
# Ignore development Dockerfile
|
||||
Dockerfile
|
||||
Dockerfile.dev
|
||||
docker-dev.sh
|
||||
|
||||
# Ignore the data directory
|
||||
data/
|
||||
|
||||
3
.gitattributes
vendored
@@ -1 +1,2 @@
|
||||
resources/web/**/* linguist-vendored
|
||||
resources/web/**/* linguist-vendored
|
||||
resources/web2/**/* linguist-vendored
|
||||
156
.github/workflows/build.yml
vendored
@@ -55,6 +55,13 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: lejianwen/rustdesk-api-web
|
||||
path: rustdesk-api-web
|
||||
ref: master
|
||||
|
||||
- name: Set up Go environment
|
||||
uses: actions/setup-go@v4
|
||||
@@ -66,19 +73,26 @@ jobs:
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
|
||||
- name: build rustdesk-api-web
|
||||
working-directory: rustdesk-api-web
|
||||
run: |
|
||||
git clone ${{ env.WEBCLIENT_SOURCE_LOCATION }}
|
||||
cd rustdesk-api-web
|
||||
npm install
|
||||
npm run build
|
||||
mkdir ../resources/admin/ -p
|
||||
mkdir -p ../resources/admin/
|
||||
cp -ar dist/* ../resources/admin/
|
||||
|
||||
- name: tidy
|
||||
run: go mod tidy
|
||||
|
||||
- name: Get tag version
|
||||
run: |
|
||||
TAG_VERSION="${GITHUB_REF##*/}"
|
||||
VERSION="${TAG_VERSION#v}"
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Write version to resources/version
|
||||
run: echo $VERSION > resources/version
|
||||
|
||||
- name: swag
|
||||
run: |
|
||||
go install github.com/swaggo/swag/cmd/swag@latest
|
||||
@@ -96,6 +110,8 @@ jobs:
|
||||
if [ "${{ matrix.job.goos }}" = "windows" ]; then
|
||||
sudo apt-get install gcc-mingw-w64-x86-64 zip -y
|
||||
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} CC=x86_64-w64-mingw32-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain.exe ./cmd/apimain.go
|
||||
echo @echo off > release/start.bat
|
||||
echo cmd /c \"%~dp0apimain.exe\" >> release/start.bat
|
||||
zip -r ${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}} ./release
|
||||
else
|
||||
if [ "${{ matrix.job.platform }}" = "arm64" ]; then
|
||||
@@ -121,15 +137,92 @@ jobs:
|
||||
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
|
||||
path: |
|
||||
${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}}
|
||||
|
||||
- name: Upload to GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}}
|
||||
# tag_name: ${{ env.LATEST_TAG }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate Changelog
|
||||
run: npx changelogithub # or changelogithub@0.12 if ensure the stable result
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
|
||||
deb-package:
|
||||
name: debian package - ${{ matrix.job.platform }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- { platform: "amd64", goos: "linux", debian_platform: "amd64", crossbuild_package: ""}
|
||||
- { platform: "arm64", goos: "linux", debian_platform: "arm64", crossbuild_package: "crossbuild-essential-arm64" }
|
||||
- { platform: "armv7l", goos: "linux", debian_platform: "armhf", crossbuild_package: "crossbuild-essential-armhf" }
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Create packaging env
|
||||
run: |
|
||||
sudo apt update
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y devscripts build-essential debhelper pkg-config ${{ matrix.job.crossbuild_package }}
|
||||
mkdir -p debian-build/${{ matrix.job.platform }}/bin
|
||||
|
||||
- name: Get tag version
|
||||
id: get_tag
|
||||
run: |
|
||||
TAG_VERSION="${GITHUB_REF##*/}"
|
||||
VERSION="${TAG_VERSION#v}"
|
||||
echo "TAG_VERSION=$TAG_VERSION" >> $GITHUB_ENV
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Update changelog
|
||||
run: |
|
||||
DATE=$(date -R)
|
||||
sed -i "1i rustdesk-api-server (${VERSION}) stable; urgency=medium\n\n * Automatically generated release for version ${VERSION}.\n\n -- GitHub Actions <actions@github.com> ${DATE}\n" debian/changelog
|
||||
|
||||
- name: Download binaries
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
|
||||
path: .
|
||||
|
||||
- name: Unzip binaries
|
||||
run: |
|
||||
mkdir -p ${{ matrix.job.platform }}
|
||||
tar -xzf ${{ matrix.job.goos }}-${{ matrix.job.platform }}.tar.gz -C ${{ matrix.job.platform }}
|
||||
|
||||
- name: Build package for ${{ matrix.job.platform }} arch
|
||||
run: |
|
||||
mv ${{ matrix.job.platform }}/release/apimain debian-build/${{ matrix.job.platform }}/bin/rustdesk-api
|
||||
chmod -v a+x debian-build/${{ matrix.job.platform }}/bin/*
|
||||
mkdir -p data
|
||||
cp -vr debian systemd conf data resources runtime debian-build/${{ matrix.job.platform }}/
|
||||
cat debian/control.tpl | sed 's/{{ ARCH }}/${{ matrix.job.debian_platform }}/' > debian-build/${{ matrix.job.platform }}/debian/control
|
||||
cd debian-build/${{ matrix.job.platform }}/
|
||||
debuild -i -us -uc -b -a${{ matrix.job.debian_platform}}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: rustdesk-api-${{ matrix.job.debian_platform }}
|
||||
path: |
|
||||
debian-build/*.deb
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
debian-build/rustdesk-api-server_*_${{ matrix.job.debian_platform }}.deb
|
||||
|
||||
docker:
|
||||
name: Push Docker Image
|
||||
needs: build
|
||||
@@ -191,7 +284,6 @@ jobs:
|
||||
run: |
|
||||
mkdir -p ${{ matrix.job.platform }}
|
||||
tar -xzf ${{ matrix.job.goos }}-${{ matrix.job.platform }}.tar.gz -C ${{ matrix.job.platform }}
|
||||
file ${{ matrix.job.platform }}/apimain
|
||||
|
||||
- name: Build and push Docker image to Docker Hub ${{ matrix.job.platform }}
|
||||
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only run this step if SKIP_DOCKER_HUB is false
|
||||
@@ -209,6 +301,21 @@ jobs:
|
||||
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
- name: Build and push Docker Full S6 image to Docker Hub ${{ matrix.job.platform }}
|
||||
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only run this step if SKIP_DOCKER_HUB is false
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: "."
|
||||
file: ./Dockerfile_full_s6
|
||||
platforms: ${{ matrix.job.docker_platform }}
|
||||
push: true
|
||||
provenance: false
|
||||
build-args: |
|
||||
BUILDARCH=${{ matrix.job.platform }}
|
||||
tags: |
|
||||
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-${{ matrix.job.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
- name: Build and push Docker image to GHCR ${{ matrix.job.platform }}
|
||||
if: ${{ env.SKIP_GHCR == 'false' }} # Only run this step if SKIP_GHCR is false
|
||||
uses: docker/build-push-action@v5
|
||||
@@ -225,6 +332,21 @@ jobs:
|
||||
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
- name: Build and push Docker Full S6 image to GHCR ${{ matrix.job.platform }}
|
||||
if: ${{ env.SKIP_GHCR == 'false' }} # Only run this step if SKIP_GHCR is false
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: "."
|
||||
file: ./Dockerfile
|
||||
platforms: ${{ matrix.job.docker_platform }}
|
||||
push: true
|
||||
provenance: false
|
||||
build-args: |
|
||||
BUILDARCH=${{ matrix.job.platform }}
|
||||
tags: |
|
||||
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-${{ matrix.job.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
#
|
||||
docker-manifest:
|
||||
name: Push Docker Manifest
|
||||
@@ -295,4 +417,26 @@ jobs:
|
||||
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-armv7l,
|
||||
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-arm64
|
||||
push: true
|
||||
amend: true
|
||||
|
||||
- name: Create and push Full S6 manifest Docker Hub (:version)
|
||||
if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
|
||||
uses: Noelware/docker-manifest-action@master
|
||||
with:
|
||||
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:full-s6
|
||||
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-amd64,
|
||||
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-armv7l,
|
||||
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-arm64
|
||||
push: true
|
||||
amend: true
|
||||
|
||||
- name: Create and push Full S6 manifest GHCR (:latest)
|
||||
if: ${{ env.SKIP_GHCR == 'false' }}
|
||||
uses: Noelware/docker-manifest-action@master
|
||||
with:
|
||||
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:full-s6
|
||||
extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-amd64,
|
||||
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-armv7l,
|
||||
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:full-s6-arm64
|
||||
push: true
|
||||
amend: true
|
||||
337
.github/workflows/build_test.yml
vendored
Normal file
@@ -0,0 +1,337 @@
|
||||
name: Build Test
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
BASE_IMAGE_NAMESPACE:
|
||||
description: 'Base image namespace (Default: Your Github username)'
|
||||
required: false
|
||||
default: ''
|
||||
DOCKERHUB_IMAGE_NAMESPACE:
|
||||
description: 'Docker Hub image namespace (Default: Your Github username)'
|
||||
required: false
|
||||
default: ''
|
||||
GHCR_IMAGE_NAMESPACE:
|
||||
description: 'GitHub Container Registry image namespace (Default: Your Github username)'
|
||||
required: false
|
||||
default: ''
|
||||
SKIP_DOCKER_HUB:
|
||||
description: 'Set to true to skip pushing to Docker Hub (default: false)'
|
||||
required: false
|
||||
default: 'false'
|
||||
SKIP_GHCR:
|
||||
description: 'Set to true to skip pushing to GHCR (default: false)'
|
||||
required: false
|
||||
default: 'false'
|
||||
WEBCLIENT_SOURCE_LOCATION:
|
||||
description: 'Web Client API Repository'
|
||||
required: true
|
||||
default: 'https://github.com/lejianwen/rustdesk-api-web'
|
||||
|
||||
env:
|
||||
LATEST_TAG: latest
|
||||
WEBCLIENT_SOURCE_LOCATION: ${{ github.event.inputs.WEBCLIENT_SOURCE_LOCATION || 'https://github.com/lejianwen/rustdesk-api-web' }}
|
||||
BASE_IMAGE_NAMESPACE: ${{ github.event.inputs.BASE_IMAGE_NAMESPACE || github.actor }}
|
||||
DOCKERHUB_IMAGE_NAMESPACE: ${{ github.event.inputs.DOCKERHUB_IMAGE_NAMESPACE || github.actor }}
|
||||
GHCR_IMAGE_NAMESPACE: ${{ github.event.inputs.GHCR_IMAGE_NAMESPACE || github.actor }}
|
||||
SKIP_DOCKER_HUB: ${{ github.event.inputs.SKIP_DOCKER_HUB || 'false' }}
|
||||
SKIP_GHCR: ${{ github.event.inputs.SKIP_GHCR || 'false' }}
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- { platform: "amd64", goos: "linux", file_ext: "tar.gz" }
|
||||
- { platform: "arm64", goos: "linux", file_ext: "tar.gz" }
|
||||
- { platform: "armv7l", goos: "linux", file_ext: "tar.gz" }
|
||||
- { platform: "amd64", goos: "windows", file_ext: "zip" }
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: lejianwen/rustdesk-api-web
|
||||
path: rustdesk-api-web
|
||||
ref: master
|
||||
|
||||
- name: Set up Go environment
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.22' # 选择 Go 版本
|
||||
|
||||
- name: Set up npm
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: build rustdesk-api-web
|
||||
working-directory: rustdesk-api-web
|
||||
run: |
|
||||
npm install
|
||||
npm run build
|
||||
mkdir -p ../resources/admin/
|
||||
cp -ar dist/* ../resources/admin/
|
||||
|
||||
- name: tidy
|
||||
run: go mod tidy
|
||||
|
||||
- name: swag
|
||||
run: |
|
||||
go install github.com/swaggo/swag/cmd/swag@latest
|
||||
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
|
||||
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
|
||||
|
||||
- name: Build for ${{ matrix.job.goos }}-${{ matrix.job.platform }}
|
||||
run: |
|
||||
mkdir release -p
|
||||
cp -ar resources release/
|
||||
cp -ar docs release/
|
||||
cp -ar conf release/
|
||||
mkdir -p release/data
|
||||
mkdir -p release/runtime
|
||||
if [ "${{ matrix.job.goos }}" = "windows" ]; then
|
||||
sudo apt-get install gcc-mingw-w64-x86-64 zip -y
|
||||
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} CC=x86_64-w64-mingw32-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain.exe ./cmd/apimain.go
|
||||
echo @echo off > release/start.bat
|
||||
echo cmd /c \"%~dp0apimain.exe\" >> release/start.bat
|
||||
zip -r ${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}} ./release
|
||||
else
|
||||
if [ "${{ matrix.job.platform }}" = "arm64" ]; then
|
||||
wget https://musl.cc/aarch64-linux-musl-cross.tgz
|
||||
tar -xf aarch64-linux-musl-cross.tgz
|
||||
export PATH=$PATH:$PWD/aarch64-linux-musl-cross/bin
|
||||
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} CC=aarch64-linux-musl-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
|
||||
elif [ "${{ matrix.job.platform }}" = "armv7l" ]; then
|
||||
wget https://musl.cc/armv7l-linux-musleabihf-cross.tgz
|
||||
tar -xf armv7l-linux-musleabihf-cross.tgz
|
||||
export PATH=$PATH:$PWD/armv7l-linux-musleabihf-cross/bin
|
||||
GOOS=${{ matrix.job.goos }} GOARCH=arm GOARM=7 CC=armv7l-linux-musleabihf-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
|
||||
else
|
||||
sudo apt-get install musl musl-dev musl-tools -y
|
||||
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} CC=musl-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
|
||||
fi
|
||||
tar -czf ${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}} ./release
|
||||
fi
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
|
||||
path: |
|
||||
${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}}
|
||||
|
||||
- name: Upload to GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}}
|
||||
tag_name: test
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
deb-package:
|
||||
name: debian package - ${{ matrix.job.platform }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- { platform: "amd64", goos: "linux", debian_platform: "amd64", crossbuild_package: ""}
|
||||
- { platform: "arm64", goos: "linux", debian_platform: "arm64", crossbuild_package: "crossbuild-essential-arm64" }
|
||||
- { platform: "armv7l", goos: "linux", debian_platform: "armhf", crossbuild_package: "crossbuild-essential-armhf" }
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Create packaging env
|
||||
run: |
|
||||
sudo apt update
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y devscripts build-essential debhelper pkg-config ${{ matrix.job.crossbuild_package }}
|
||||
mkdir -p debian-build/${{ matrix.job.platform }}/bin
|
||||
|
||||
- name: Download binaries
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
|
||||
path: .
|
||||
|
||||
- name: Unzip binaries
|
||||
run: |
|
||||
mkdir -p ${{ matrix.job.platform }}
|
||||
tar -xzf ${{ matrix.job.goos }}-${{ matrix.job.platform }}.tar.gz -C ${{ matrix.job.platform }}
|
||||
|
||||
- name: Build package for ${{ matrix.job.platform }} arch
|
||||
run: |
|
||||
mv ${{ matrix.job.platform }}/release/apimain debian-build/${{ matrix.job.platform }}/bin/rustdesk-api
|
||||
chmod -v a+x debian-build/${{ matrix.job.platform }}/bin/*
|
||||
mkdir -p data
|
||||
cp -vr debian systemd conf data resources runtime debian-build/${{ matrix.job.platform }}/
|
||||
cat debian/control.tpl | sed 's/{{ ARCH }}/${{ matrix.job.debian_platform }}/' > debian-build/${{ matrix.job.platform }}/debian/control
|
||||
cd debian-build/${{ matrix.job.platform }}/
|
||||
debuild -i -us -uc -b -a${{ matrix.job.debian_platform}}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: rustdesk-api-${{ matrix.job.debian_platform }}
|
||||
path: |
|
||||
debian-build/*.deb
|
||||
|
||||
- name: Upload to GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: test
|
||||
files: |
|
||||
debian-build/rustdesk-api-server_*_${{ matrix.job.debian_platform }}.deb
|
||||
|
||||
docker:
|
||||
name: Push Docker Image
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- { platform: "amd64", goos: "linux", docker_platform: "linux/amd64" }
|
||||
- { platform: "arm64", goos: "linux", docker_platform: "linux/arm64" }
|
||||
- { platform: "armv7l", goos: "linux", docker_platform: "linux/arm/v7" }
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only log in if SKIP_DOCKER_HUB is false
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
if: ${{ env.SKIP_GHCR == 'false' }} # Only log in if GHCR push is enabled
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract version from tag
|
||||
id: vars
|
||||
run: |
|
||||
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
|
||||
echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "TAG=test" >> $GITHUB_ENV # Default to 'test' if not a tag
|
||||
fi
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api
|
||||
|
||||
- name: Download binaries
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
|
||||
path: ./
|
||||
|
||||
- name: Unzip binaries
|
||||
run: |
|
||||
mkdir -p ${{ matrix.job.platform }}
|
||||
tar -xzf ${{ matrix.job.goos }}-${{ matrix.job.platform }}.tar.gz -C ${{ matrix.job.platform }}
|
||||
|
||||
- name: Build and push Docker image to Docker Hub ${{ matrix.job.platform }}
|
||||
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only run this step if SKIP_DOCKER_HUB is false
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: "."
|
||||
file: ./Dockerfile
|
||||
platforms: ${{ matrix.job.docker_platform }}
|
||||
push: true
|
||||
provenance: false
|
||||
build-args: |
|
||||
BUILDARCH=${{ matrix.job.platform }}
|
||||
tags: |
|
||||
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
- name: Build and push Docker image to GHCR ${{ matrix.job.platform }}
|
||||
if: ${{ env.SKIP_GHCR == 'false' }} # Only run this step if SKIP_GHCR is false
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: "."
|
||||
file: ./Dockerfile
|
||||
platforms: ${{ matrix.job.docker_platform }}
|
||||
push: true
|
||||
provenance: false
|
||||
build-args: |
|
||||
BUILDARCH=${{ matrix.job.platform }}
|
||||
tags: |
|
||||
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
#
|
||||
docker-manifest:
|
||||
name: Push Docker Manifest
|
||||
needs: docker
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Extract version from tag
|
||||
id: vars
|
||||
run: |
|
||||
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
|
||||
echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "TAG=test" >> $GITHUB_ENV # Default to 'test' if not a tag
|
||||
fi
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only log in if Docker Hub push is enabled
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
if: ${{ env.SKIP_GHCR == 'false' }} # Only log in if GHCR push is enabled
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create and push manifest Docker Hub (:version)
|
||||
if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
|
||||
uses: Noelware/docker-manifest-action@master
|
||||
with:
|
||||
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
|
||||
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
|
||||
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-armv7l,
|
||||
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-arm64
|
||||
push: true
|
||||
|
||||
- name: Create and push manifest GHCR (:version)
|
||||
if: ${{ env.SKIP_GHCR == 'false' }}
|
||||
uses: Noelware/docker-manifest-action@master
|
||||
with:
|
||||
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
|
||||
extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
|
||||
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-armv7l,
|
||||
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-arm64
|
||||
push: true
|
||||
amend: true
|
||||
95
.github/workflows/release.yml
vendored
@@ -1,95 +0,0 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# tags:
|
||||
# - 'v*.*.*' # 当推送带有版本号的 tag(例如 v1.0.0)时触发工作流
|
||||
#on:
|
||||
# push:
|
||||
# branches: [ "master" ]
|
||||
# pull_request:
|
||||
# branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [ linux, windows ] # 指定要构建的操作系统
|
||||
goarch: [ amd64 ] # 指定架构
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go environment
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.22' # 选择 Go 版本
|
||||
|
||||
- name: Set up npm
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: install gcc zip musl
|
||||
run: |
|
||||
if [ "${{ matrix.goos }}" = "windows" ]; then
|
||||
sudo apt-get install gcc-mingw-w64-x86-64 zip -y
|
||||
else
|
||||
sudo apt-get install musl musl-dev musl-tools -y
|
||||
fi
|
||||
|
||||
|
||||
- name: build rustdesk-api-web
|
||||
run: |
|
||||
git clone https://github.com/lejianwen/rustdesk-api-web
|
||||
cd rustdesk-api-web
|
||||
npm install
|
||||
npm run build
|
||||
mkdir ../resources/admin/ -p
|
||||
cp -ar dist/* ../resources/admin/
|
||||
|
||||
- name: tidy
|
||||
run: go mod tidy
|
||||
|
||||
|
||||
- name: swag
|
||||
run: |
|
||||
go install github.com/swaggo/swag/cmd/swag@latest
|
||||
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
|
||||
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
|
||||
|
||||
- name: Build for ${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
run: |
|
||||
mkdir release -p
|
||||
cp -ar resources release/
|
||||
cp -ar docs release/
|
||||
cp -ar conf release/
|
||||
mkdir -p release/data
|
||||
mkdir -p release/runtime
|
||||
if [ "${{ matrix.goos }}" = "windows" ]; then
|
||||
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} CC=x86_64-w64-mingw32-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain.exe ./cmd/apimain.go
|
||||
zip -r ${{ matrix.goos}}-${{ matrix.goarch }}.zip ./release
|
||||
else
|
||||
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} CC=musl-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
|
||||
tar -czf ${{ matrix.goos}}-${{ matrix.goarch }}.tar.gz ./release
|
||||
fi
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: myapp-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
path: |
|
||||
${{ matrix.goos}}-${{ matrix.goarch }}.tar.gz
|
||||
${{ matrix.goos}}-${{ matrix.goarch }}.zip
|
||||
|
||||
- name: Upload to GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
${{ matrix.goos}}-${{ matrix.goarch }}.tar.gz
|
||||
${{ matrix.goos}}-${{ matrix.goarch }}.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
8
.gitignore
vendored
@@ -1,10 +1,8 @@
|
||||
.idea
|
||||
runtime/*
|
||||
!runtime/cache/.gitignore
|
||||
!runtime
|
||||
!runtime/cache/.gitkeep
|
||||
go.sum
|
||||
resources/*
|
||||
!resources/public/upload/.gitignore
|
||||
!resources/web
|
||||
!resources/i18n
|
||||
resources/admin
|
||||
release
|
||||
data
|
||||
@@ -2,9 +2,8 @@ FROM alpine
|
||||
|
||||
ARG BUILDARCH
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache tzdata file
|
||||
RUN apk add --no-cache tzdata
|
||||
COPY ./${BUILDARCH}/release /app/
|
||||
RUN file /app/apimain
|
||||
VOLUME /app/data
|
||||
|
||||
EXPOSE 21114
|
||||
|
||||
@@ -12,17 +12,19 @@ WORKDIR /app
|
||||
# Step 1: Copy the source code
|
||||
COPY . .
|
||||
|
||||
# use --mount=type=cache,target=/go/pkg/mod to cache the go mod
|
||||
# Step 2: Download dependencies
|
||||
RUN go mod tidy && go mod download
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
go mod tidy && go mod download && go install github.com/swaggo/swag/cmd/swag@latest
|
||||
|
||||
|
||||
# Step 3: Install swag and Run the build script
|
||||
RUN go install github.com/swaggo/swag/cmd/swag@latest && \
|
||||
# Step 3: Run swag build script
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin && \
|
||||
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
|
||||
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
|
||||
|
||||
# Build the Go application with CGO enabled and specified ldflags
|
||||
RUN CGO_ENABLED=1 GOOS=linux go build -a \
|
||||
# Step 4: Build the Go application with CGO enabled and specified ldflags
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
CGO_ENABLED=1 GOOS=linux go build -a \
|
||||
-ldflags "-s -w --extldflags '-static -fpic'" \
|
||||
-installsuffix cgo -o release/apimain cmd/apimain.go
|
||||
|
||||
@@ -32,13 +34,27 @@ FROM node:18-alpine AS builder-admin-frontend
|
||||
# Set working directory
|
||||
WORKDIR /frontend
|
||||
|
||||
RUN apk update && apk add git --no-cache
|
||||
ARG COUNTRY
|
||||
# Install required tools without caching index to minimize image size
|
||||
RUN if [ "$COUNTRY" = "CN" ] ; then \
|
||||
echo "It is in China, updating the repositories"; \
|
||||
sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.tuna.tsinghua.edu.cn/alpine#g' /etc/apk/repositories; \
|
||||
fi && \
|
||||
apk update && apk add --no-cache git
|
||||
|
||||
ARG FREONTEND_GIT_REPO=https://github.com/lejianwen/rustdesk-api-web.git
|
||||
ARG FRONTEND_GIT_BRANCH=master
|
||||
# Clone the frontend repository
|
||||
RUN git clone https://github.com/lejianwen/rustdesk-api-web .
|
||||
|
||||
# Install npm dependencies and build the frontend
|
||||
RUN npm install && npm run build
|
||||
RUN git clone -b $FRONTEND_GIT_BRANCH $FREONTEND_GIT_REPO .
|
||||
|
||||
# Install required tools without caching index to minimize image size
|
||||
RUN if [ "$COUNTRY" = "CN" ] ; then \
|
||||
echo "It is in China, updating NPM_CONFIG_REGISTRY"; \
|
||||
export NPM_CONFIG_REGISTRY="https://mirrors.huaweicloud.com/repository/npm/"; \
|
||||
fi && \
|
||||
npm install && npm run build
|
||||
|
||||
|
||||
# Stage 2: Final Image
|
||||
FROM alpine:latest
|
||||
@@ -47,7 +63,13 @@ FROM alpine:latest
|
||||
WORKDIR /app
|
||||
|
||||
# Install necessary runtime dependencies
|
||||
RUN apk add --no-cache tzdata file
|
||||
# Install required tools without caching index to minimize image size
|
||||
ARG COUNTRY
|
||||
RUN if [ "$COUNTRY" = "CN" ] ; then \
|
||||
echo "It is in China, updating the repositories"; \
|
||||
sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.tuna.tsinghua.edu.cn/alpine#g' /etc/apk/repositories; \
|
||||
fi && \
|
||||
apk update && apk add --no-cache tzdata file
|
||||
|
||||
# Copy the built application and resources from the builder stage
|
||||
COPY --from=builder-backend /app/release /app/
|
||||
|
||||
38
Dockerfile_full_s6
Normal file
@@ -0,0 +1,38 @@
|
||||
FROM rustdesk/rustdesk-server-s6:latest AS server
|
||||
|
||||
FROM alpine
|
||||
|
||||
ARG BUILDARCH
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache tzdata
|
||||
COPY ./${BUILDARCH}/release /app/
|
||||
|
||||
COPY --from=server /init /init
|
||||
COPY --from=server /etc/s6-overlay /etc/s6-overlay
|
||||
COPY --from=server /package /package
|
||||
COPY --from=server /usr/bin/healthcheck.sh /usr/bin/healthcheck.sh
|
||||
COPY --from=server /usr/bin/hbbr /usr/bin/hbbr
|
||||
COPY --from=server /usr/bin/hbbs /usr/bin/hbbs
|
||||
COPY --from=server /usr/bin/rustdesk-utils /usr/bin/rustdesk-utils
|
||||
COPY --from=server /command /command
|
||||
|
||||
RUN \
|
||||
mkdir -p /etc/s6-overlay/s6-rc.d/api && \
|
||||
echo -e "key-secret\nhbbs" > /etc/s6-overlay/s6-rc.d/api/dependencies && \
|
||||
echo "longrun" > /etc/s6-overlay/s6-rc.d/api/type && \
|
||||
echo "#!/command/with-contenv sh" > /etc/s6-overlay/s6-rc.d/api/run && \
|
||||
echo "cd /app" >> /etc/s6-overlay/s6-rc.d/api/run && \
|
||||
echo "./apimain" >> /etc/s6-overlay/s6-rc.d/api/run && \
|
||||
touch /etc/s6-overlay/s6-rc.d/user/contents.d/api && \
|
||||
echo "/package/admin/s6/command/s6-svstat /run/s6-rc/servicedirs/api || exit 1" >> /usr/bin/healthcheck.sh && \
|
||||
ln -s /run /var/run
|
||||
|
||||
ENV RELAY=relay.example.com
|
||||
ENV ENCRYPTED_ONLY=0
|
||||
|
||||
VOLUME /data
|
||||
VOLUME /app/data
|
||||
|
||||
EXPOSE 21114 21115 21116 21116/udp 21117 21118 21119
|
||||
|
||||
ENTRYPOINT ["/init"]
|
||||
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024-present Lejianwen and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
441
README.md
@@ -34,77 +34,37 @@
|
||||
- 快速使用web client
|
||||
- i18n
|
||||
- 通过 web client 分享给游客
|
||||
- server控制(一些官方的简单的指令 [WIKI](https://github.com/lejianwen/rustdesk-api/wiki/Rustdesk-Command))
|
||||
- Web Client
|
||||
- 自动获取API server
|
||||
- 自动获取ID服务器和KEY
|
||||
- 自动获取地址簿
|
||||
- 游客通过临时分享链接直接远程到设备
|
||||
|
||||
## 使用前准备
|
||||
|
||||
### [Rustdesk](https://github.com/rustdesk/rustdesk)
|
||||
|
||||
#### PC客户端使用的是 ***1.3.0***,经测试 ***1.2.6+*** 都可以
|
||||
|
||||
#### 关于PC端链接超时或者链接不上的问题以及解决方案
|
||||
##### 链接不上或者超时
|
||||
因为server端相对于客户端落后版本,server不会响应客户端的`secure_tcp`请求,所以客户端超时。
|
||||
相关代码代码位置在`https://github.com/rustdesk/rustdesk/blob/master/src/client.rs#L322`
|
||||
```rust
|
||||
if !key.is_empty() && !token.is_empty() {
|
||||
// mainly for the security of token
|
||||
allow_err!(secure_tcp(&mut socket, key).await);
|
||||
}
|
||||
```
|
||||
可看到当`key`和`token`都不为空时,会调用`secure_tcp`,但是server端不会响应,所以客户端超时
|
||||
`secure_tcp` 代码位置在 `https://github.com/rustdesk/rustdesk/blob/master/src/common.rs#L1203`
|
||||
|
||||
##### 4种解决方案
|
||||
1. server端指定key。
|
||||
- 优点:简单
|
||||
- 缺点:链接不是加密的
|
||||
```bash
|
||||
hbbs -r <relay-server-ip[:port]> -k <key>
|
||||
hbbr -k <key>
|
||||
```
|
||||
比如
|
||||
```bash
|
||||
hbbs -r <relay-server-ip[:port]> -k abc1234567
|
||||
hbbr -k abc1234567
|
||||
```
|
||||
2. server端使用系统生成的key,或者自定义的密钥对,但如果client已登录,链接时容易超时或者链接不上,可以退出登录后再链接就可以了,webclient可以不用退出登录
|
||||
- 优点:链接加密
|
||||
- 缺点:操作麻烦
|
||||
3. server端使用系统生成的key,或者自定义的密钥对,fork官方客户端的代码将`secure_tcp`修改成直接返回,然后通过`Github Actions`编译,下载编译后的客户端。
|
||||
参考[官方文档](https://rustdesk.com/docs/en/dev/build/all/)
|
||||
- 优点:链接加密,可以自定义客户端一些功能,编译后直接可用
|
||||
- 缺点:需要自己fork代码,编译,有点难度
|
||||
4. 使用[我fork的代码](https://github.com/lejianwen/rustdesk),已经修改了`secure_tcp`,可以直接下载使用,[下载地址](https://github.com/lejianwen/rustdesk/releases)
|
||||
- 优点:代码改动可查看,`Github Actions`编译,链接加密,直接下载使用
|
||||
- 缺点:可能跟不上官方版本更新
|
||||
|
||||
***对链接加密要求不高的可以使用`1`,对链接加密要求高的可以使用`3`或`4`***
|
||||
- v2 Preview
|
||||
- CLI
|
||||
- 重置管理员密码
|
||||
|
||||
## 功能
|
||||
|
||||
### API 服务
|
||||
基本实现了PC端基础的接口。支持Personal版本接口,可以通过配置文件`rustdesk.personal`或环境变量`RUSTDESK_API_RUSTDESK_PERSONAL`来控制是否启用
|
||||
|
||||
#### 登录
|
||||
|
||||
- 添加了`github`, `google` 以及`OIDC`授权登录,需要在后台配置好就可以用了,具体可看后台OAuth配置
|
||||
- 添加了web后台授权登录,点击后直接登录后台就自动登录客户端了
|
||||
|
||||

|
||||
|
||||
#### 地址簿
|
||||
|
||||

|
||||
|
||||
#### 群组
|
||||
群组分为`共享组`和`普通组`,共享组中所有人都能看到小组成员的设备,普通组只有管理员能看到所有小组成员的设备
|
||||
|
||||

|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%" align="center" colspan="2"><b>登录</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" align="center" colspan="2"><img src="docs/pc_login.png"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" align="center"><b>地址簿</b></td>
|
||||
<td width="50%" align="center"><b>群组</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" align="center"><img src="docs/pc_ab.png"></td>
|
||||
<td width="50%" align="center"><img src="docs/pc_gr.png"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Web Admin:
|
||||
|
||||
@@ -116,15 +76,16 @@
|
||||

|
||||
2. 普通用户界面
|
||||

|
||||
|
||||
右上角可以更改密码,可以切换语言,可以切换`白天/黑夜`模式
|
||||
|
||||

|
||||
|
||||
3. 分组可以自定义,方便管理,暂时支持两种类型: `共享组` 和 `普通组`
|
||||

|
||||
4. 可以直接打开webclient,方便使用;也可以分享给游客,游客可以直接通过webclient远程到设备
|
||||
|
||||
3. 每个用户可以多个地址簿,也可以将地址簿共享给其他用户
|
||||
4. 分组可以自定义,方便管理,暂时支持两种类型: `共享组` 和 `普通组`
|
||||
5. 可以直接打开webclient,方便使用;也可以分享给游客,游客可以直接通过webclient远程到设备
|
||||

|
||||
5. Oauth,支持了`Github`, `Google` 以及 `OIDC`, 需要创建一个`OAuth App`,然后配置到后台
|
||||
6. Oauth,支持了`Github`, `Google` 以及 `OIDC`, 需要创建一个`OAuth App`,然后配置到后台
|
||||

|
||||
- 对于`Google` 和 `Github`, `Issuer` 和 `Scopes`不需要填写.
|
||||
- 对于`OIDC`, `Issuer`是必须的。`Scopes`是可选的,默认为 `openid,profile,email`. 确保可以获取 `sub`,`email` 和`preferred_username`
|
||||
@@ -132,14 +93,32 @@
|
||||
中创建,地址 [https://github.com/settings/developers](https://github.com/settings/developers)
|
||||
- `Authorization callback URL`填写`http://<your server[:port]>/api/oauth/callback`
|
||||
,比如`http://127.0.0.1:21114/api/oauth/callback`
|
||||
7. 登录日志
|
||||
8. 链接日志
|
||||
9. 文件传输日志
|
||||
10. server控制
|
||||
|
||||
- `简易模式`,已经界面化了一些简单的指令,可以直接在后台执行
|
||||

|
||||
|
||||
- `高级模式`,直接在后台执行指令
|
||||
* 可以官方指令
|
||||
* 可以添加自定义指令
|
||||
* 可以执行自定义指令
|
||||
|
||||

|
||||
|
||||
|
||||
### Web Client:
|
||||
|
||||
1. 如果已经登录了后台,web client将自动直接登录
|
||||
2. 如果没登录后台,点击右上角登录即可,api server已经自动配置好了
|
||||

|
||||
3. 登录后,会自动同步ID服务器和KEY
|
||||
4. 登录后,会将地址簿自动保存到web client中,方便使用
|
||||
5. 现已支持`v2 Preview`,访问路径是`/webclient2`
|
||||

|
||||
6. `v2 preview` 部署,参考[WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
|
||||
|
||||
|
||||
### 自动化文档: 使用 Swag 生成 API 文档,方便开发者理解和使用 API。
|
||||
|
||||
@@ -147,6 +126,17 @@
|
||||
2. PC端文档 `<youer server[:port]>/swagger/index.html`
|
||||

|
||||
|
||||
### CLI
|
||||
```bash
|
||||
# 查看帮助
|
||||
./apimain -h
|
||||
```
|
||||
|
||||
#### 重置管理员密码
|
||||
```bash
|
||||
./apimain reset-admin-pwd <pwd>
|
||||
```
|
||||
|
||||
## 安装与运行
|
||||
|
||||
### 相关配置
|
||||
@@ -159,6 +149,8 @@
|
||||
lang: "en"
|
||||
app:
|
||||
web-client: 1 # 1:启用 0:禁用
|
||||
register: false #是否开启注册
|
||||
show-swagger: 0 #是否显示swagger文档
|
||||
gin:
|
||||
api-addr: "0.0.0.0:21114"
|
||||
mode: "release"
|
||||
@@ -186,36 +178,51 @@ logger:
|
||||
proxy:
|
||||
enable: false
|
||||
host: ""
|
||||
jwt:
|
||||
key: ""
|
||||
expire-duration: 360000
|
||||
```
|
||||
|
||||
### 环境变量
|
||||
变量名前缀是`RUSTDESK_API`,环境变量如果存在将覆盖配置文件中的配置
|
||||
|
||||
| 变量名 | 说明 | 示例 |
|
||||
|------------------------------------|--------------------------------------|-----------------------------|
|
||||
| TZ | 时区 | Asia/Shanghai |
|
||||
| RUSTDESK_API_LANG | 语言 | `en`,`zh-CN` |
|
||||
| RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 |
|
||||
| -----GIN配置----- | ---------- | ---------- |
|
||||
| RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表,以`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 |
|
||||
| -----------GORM配置---------------- | ------------------------------------ | --------------------------- |
|
||||
| RUSTDESK_API_GORM_TYPE | 数据库类型sqlite或者mysql,默认sqlite | sqlite |
|
||||
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | 数据库最大空闲连接数 | 10 |
|
||||
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | 数据库最大打开连接数 | 100 |
|
||||
| RUSTDESK_API_RUSTDESK_PERSONAL | 是否启用个人版API, 1:启用,0:不启用; 默认启用 | 1 |
|
||||
| -----MYSQL配置----- | ---------- | ---------- |
|
||||
| RUSTDESK_API_MYSQL_USERNAME | mysql用户名 | root |
|
||||
| RUSTDESK_API_MYSQL_PASSWORD | mysql密码 | 111111 |
|
||||
| RUSTDESK_API_MYSQL_ADDR | mysql地址 | 192.168.1.66:3306 |
|
||||
| RUSTDESK_API_MYSQL_DBNAME | mysql数据库名 | rustdesk |
|
||||
| -----RUSTDESK配置----- | --------------- | ---------- |
|
||||
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk的id服务器地址 | 192.168.1.66:21116 |
|
||||
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk的relay服务器地址 | 192.168.1.66:21117 |
|
||||
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk的api服务器地址 | http://192.168.1.66:21114 |
|
||||
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk的key | 123456789 |
|
||||
| ----PROXY配置----- | --------------- | ---------- |
|
||||
| RUSTDESK_API_PROXY_ENABLE | 是否启用代理:`false`, `true` | `false` |
|
||||
| RUSTDESK_API_PROXY_HOST | 代理地址 | `http://127.0.0.1:1080` |
|
||||
| 变量名 | 说明 | 示例 |
|
||||
|---------------------------------------------------|---------------------------------------------------------|------------------------------|
|
||||
| TZ | 时区 | Asia/Shanghai |
|
||||
| RUSTDESK_API_LANG | 语言 | `en`,`zh-CN` |
|
||||
| RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 |
|
||||
| RUSTDESK_API_APP_REGISTER | 是否开启注册; `true`, `false` 默认`false` | `false` |
|
||||
| RUSTDESK_API_APP_SHOW_SWAGGER | 是否可见swagger文档;`1`显示,`0`不显示,默认`0`不显示 | `1` |
|
||||
| RUSTDESK_API_APP_TOKEN_EXPIRE | token有效时长(秒) | `3600` |
|
||||
| -----ADMIN配置----- | ---------- | ---------- |
|
||||
| RUSTDESK_API_ADMIN_TITLE | 后台标题 | `RustDesk Api Admin` |
|
||||
| RUSTDESK_API_ADMIN_HELLO | 后台欢迎语,可以使用`html` | |
|
||||
| RUSTDESK_API_ADMIN_HELLO_FILE | 后台欢迎语文件,如果内容多,使用文件更方便。<br>会覆盖`RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` |
|
||||
| -----GIN配置----- | ---------- | ---------- |
|
||||
| RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表,以`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 |
|
||||
| -----------GORM配置---------------- | ------------------------------------ | --------------------------- |
|
||||
| RUSTDESK_API_GORM_TYPE | 数据库类型sqlite或者mysql,默认sqlite | sqlite |
|
||||
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | 数据库最大空闲连接数 | 10 |
|
||||
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | 数据库最大打开连接数 | 100 |
|
||||
| RUSTDESK_API_RUSTDESK_PERSONAL | 是否启用个人版API, 1:启用,0:不启用; 默认启用 | 1 |
|
||||
| -----MYSQL配置----- | ---------- | ---------- |
|
||||
| RUSTDESK_API_MYSQL_USERNAME | mysql用户名 | root |
|
||||
| RUSTDESK_API_MYSQL_PASSWORD | mysql密码 | 111111 |
|
||||
| RUSTDESK_API_MYSQL_ADDR | mysql地址 | 192.168.1.66:3306 |
|
||||
| RUSTDESK_API_MYSQL_DBNAME | mysql数据库名 | rustdesk |
|
||||
| -----RUSTDESK配置----- | --------------- | ---------- |
|
||||
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk的id服务器地址 | 192.168.1.66:21116 |
|
||||
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk的relay服务器地址 | 192.168.1.66:21117 |
|
||||
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk的api服务器地址 | http://192.168.1.66:21114 |
|
||||
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk的key | 123456789 |
|
||||
| RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk存放key的文件 | `./conf/data/id_ed25519.pub` |
|
||||
| RUSTDESK_API_RUSTDESK_WEBCLIENT_MAGIC_QUERYONLINE | Web client v2 中是否启用新的在线状态查询方法; `1`:启用,`0`:不启用,默认不启用 | `0` |
|
||||
| ----PROXY配置----- | --------------- | ---------- |
|
||||
| RUSTDESK_API_PROXY_ENABLE | 是否启用代理:`false`, `true` | `false` |
|
||||
| RUSTDESK_API_PROXY_HOST | 代理地址 | `http://127.0.0.1:1080` |
|
||||
| ----JWT配置---- | -------- | -------- |
|
||||
| RUSTDESK_API_JWT_KEY | 自定义JWT KEY,为空则不启用JWT | |
|
||||
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT有效时间 | 360000 |
|
||||
|
||||
|
||||
### 运行
|
||||
@@ -236,189 +243,8 @@ proxy:
|
||||
lejianwen/rustdesk-api
|
||||
```
|
||||
|
||||
2. 使用`docker compose`
|
||||
- 简单示例
|
||||
```yaml
|
||||
services:
|
||||
rustdesk-api:
|
||||
container_name: rustdesk-api
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
|
||||
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
|
||||
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
|
||||
- RUSTDESK_API_RUSTDESK_KEY=<key>
|
||||
ports:
|
||||
- 21114:21114
|
||||
image: lejianwen/rustdesk-api
|
||||
volumes:
|
||||
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
|
||||
networks:
|
||||
- rustdesk-net
|
||||
restart: unless-stopped
|
||||
```
|
||||
2. 使用`docker compose`,参考[WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
|
||||
|
||||
- 根据rustdesk官方提供的示例,加上自己的rustdesk-api
|
||||
- 如果是使用的系统生成的KEY,去掉`-k <key>`参数,在启动后运行`docker-compose logs hbbs`或者`cat ./data/id_ed25519.pub`查看KEY,然后再修改`RUSTDESK_API_RUSTDESK_KEY=<key>`再执行`docker-compose up -d`
|
||||
```yaml
|
||||
networks:
|
||||
rustdesk-net:
|
||||
external: false
|
||||
services:
|
||||
hbbs:
|
||||
container_name: hbbs
|
||||
ports:
|
||||
- 21115:21115
|
||||
- 21116:21116 # 自定义 hbbs 映射端口
|
||||
- 21116:21116/udp # 自定义 hbbs 映射端口
|
||||
- 21118:21118 # web client
|
||||
image: rustdesk/rustdesk-server
|
||||
command: hbbs -r <relay-server-ip[:port]> -k <key> # 填入个人域名或 IP + hbbr 暴露端口
|
||||
volumes:
|
||||
- ./data:/root # 自定义挂载目录
|
||||
networks:
|
||||
- rustdesk-net
|
||||
depends_on:
|
||||
- hbbr
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 64M
|
||||
hbbr:
|
||||
container_name: hbbr
|
||||
ports:
|
||||
- 21117:21117 # 自定义 hbbr 映射端口
|
||||
- 21119:21119 # web client
|
||||
image: rustdesk/rustdesk-server
|
||||
command: hbbr -k <key>
|
||||
volumes:
|
||||
- ./data:/root
|
||||
networks:
|
||||
- rustdesk-net
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 64M
|
||||
rustdesk-api:
|
||||
container_name: rustdesk-api
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
|
||||
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
|
||||
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
|
||||
- RUSTDESK_API_RUSTDESK_KEY=<key>
|
||||
ports:
|
||||
- 21114:21114
|
||||
image: lejianwen/rustdesk-api
|
||||
volumes:
|
||||
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
|
||||
networks:
|
||||
- rustdesk-net
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
- S6的镜像
|
||||
- 如果使用***自定义KEY***,会需要修改启动脚本,覆盖镜像中的`/etc/s6-overlay/s6-rc.d/hbbr/run`和`/etc/s6-overlay/s6-rc.d/hbbr/run`
|
||||
1. 创建`hbbr/run`,自定义KEY才需要
|
||||
```bash
|
||||
#!/command/with-contenv sh
|
||||
cd /data
|
||||
PARAMS=
|
||||
[ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k ${KEY}"
|
||||
/usr/bin/hbbr $PARAMS
|
||||
```
|
||||
2. 创建`hbbs/run`,自定义KEY才需要
|
||||
```bash
|
||||
#!/command/with-contenv sh
|
||||
sleep 2
|
||||
cd /data
|
||||
PARAMS=
|
||||
[ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k ${KEY}"
|
||||
/usr/bin/hbbs -r $RELAY $PARAMS
|
||||
```
|
||||
3. 修改`docker-compose.yml`中的`s6`部分
|
||||
```yaml
|
||||
networks:
|
||||
rustdesk-net:
|
||||
external: false
|
||||
services:
|
||||
rustdesk-server:
|
||||
container_name: rustdesk-server
|
||||
ports:
|
||||
- 21115:21115
|
||||
- 21116:21116
|
||||
- 21116:21116/udp
|
||||
- 21117:21117
|
||||
- 21118:21118
|
||||
- 21119:21119
|
||||
image: rustdesk/rustdesk-server-s6:latest
|
||||
environment:
|
||||
- RELAY=192.168.1.66:21117
|
||||
- ENCRYPTED_ONLY=1
|
||||
- KEY=<key> #自定义KEY
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- ./hbbr/run:/etc/s6-overlay/s6-rc.d/hbbr/run
|
||||
- ./hbbs/run:/etc/s6-overlay/s6-rc.d/hbbs/run
|
||||
restart: unless-stopped
|
||||
rustdesk-api:
|
||||
container_name: rustdesk-api
|
||||
ports:
|
||||
- 21114:21114
|
||||
image: lejianwen/rustdesk-api
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
|
||||
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
|
||||
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
|
||||
- RUSTDESK_API_RUSTDESK_KEY=<key>
|
||||
volumes:
|
||||
- /data/rustdesk/api:/app/data #将数据库挂载
|
||||
networks:
|
||||
- rustdesk-net
|
||||
restart: unless-stopped
|
||||
```
|
||||
- 如果使用***系统生成的KEY***或者***自定义KEY_PUB,KEY_PRIV***,不需要修改启动脚本,但要在生成KEY后获取到KEY再`docker-compose up -d`
|
||||
```yaml
|
||||
networks:
|
||||
rustdesk-net:
|
||||
external: false
|
||||
services:
|
||||
rustdesk-server:
|
||||
container_name: rustdesk-server
|
||||
ports:
|
||||
- 21115:21115
|
||||
- 21116:21116
|
||||
- 21116:21116/udp
|
||||
- 21117:21117
|
||||
- 21118:21118
|
||||
- 21119:21119
|
||||
image: rustdesk/rustdesk-server-s6:latest
|
||||
environment:
|
||||
- RELAY=192.168.1.66:21117
|
||||
- ENCRYPTED_ONLY=1
|
||||
volumes:
|
||||
- ./data:/data
|
||||
restart: unless-stopped
|
||||
rustdesk-api:
|
||||
container_name: rustdesk-api
|
||||
ports:
|
||||
- 21114:21114
|
||||
image: lejianwen/rustdesk-api
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
|
||||
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
|
||||
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
|
||||
- RUSTDESK_API_RUSTDESK_KEY=<key> #系统生成的KEY
|
||||
volumes:
|
||||
- /data/rustdesk/api:/app/data #将数据库挂载
|
||||
networks:
|
||||
- rustdesk-net
|
||||
restart: unless-stopped
|
||||
```
|
||||
#### 下载release直接运行
|
||||
|
||||
[下载地址](https://github.com/lejianwen/rustdesk-api/releases)
|
||||
@@ -461,22 +287,61 @@ proxy:
|
||||
|
||||
6. 打开浏览器访问`http://<your server[:port]>/_admin/`,默认用户名密码为`admin`,请及时更改密码。
|
||||
|
||||
#### nginx反代
|
||||
在`nginx`中配置反代
|
||||
```
|
||||
server {
|
||||
listen <your port>;
|
||||
server_name <your server>;
|
||||
location / {
|
||||
proxy_pass http://<api-server[:port]>;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
|
||||
#### 使用我fork后的server-s6镜像运行
|
||||
|
||||
- github https://github.com/lejianwen/rustdesk-server
|
||||
- docker hub https://hub.docker.com/r/lejianwen/rustdesk-server-s6
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
rustdesk-net:
|
||||
external: false
|
||||
services:
|
||||
rustdesk:
|
||||
ports:
|
||||
- 21114:21114
|
||||
- 21115:21115
|
||||
- 21116:21116
|
||||
- 21116:21116/udp
|
||||
- 21117:21117
|
||||
- 21118:21118
|
||||
- 21119:21119
|
||||
image: lejianwen/rustdesk-server-s6:latest
|
||||
environment:
|
||||
- RELAY=<relay_server[:port]>
|
||||
- ENCRYPTED_ONLY=1
|
||||
- MUST_LOGIN=N
|
||||
- TZ=Asia/Shanghai
|
||||
- RUSTDESK_API_RUSTDESK_ID_SERVER=<id_server[:21116]>
|
||||
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=<relay_server[:21117]>
|
||||
- RUSTDESK_API_RUSTDESK_API_SERVER=http://<api_server[:21114]>
|
||||
- RUSTDESK_API_KEY_FILE=/data/id_ed25519.pub
|
||||
- RUSTDESK_API_JWT_KEY=xxxxxx # jwt key
|
||||
volumes:
|
||||
- /data/rustdesk/server:/data
|
||||
- /data/rustdesk/api:/app/data #将数据库挂载
|
||||
networks:
|
||||
- rustdesk-net
|
||||
restart: unless-stopped
|
||||
|
||||
```
|
||||
|
||||
|
||||
## 其他
|
||||
|
||||
- [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
|
||||
- [链接超时问题](https://github.com/lejianwen/rustdesk-api/issues/92)
|
||||
- [修改客户端ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)
|
||||
- [webclient来源](https://hub.docker.com/r/keyurbhole/flutter_web_desk)
|
||||
- [webclient来源](https://hub.docker.com/r/keyurbhole/flutter_web_desk)
|
||||
|
||||
|
||||
## 鸣谢
|
||||
|
||||
感谢所有做过贡献的人!
|
||||
|
||||
<a href="https://github.com/lejianwen/rustdesk-api/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" />
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
437
README_EN.md
@@ -33,80 +33,36 @@ desktop software that provides self-hosted solutions.
|
||||
- Quick access to web client
|
||||
- i18n
|
||||
- Share to guest by web client
|
||||
- Server control (some simple official commands [WIKI](https://github.com/lejianwen/rustdesk-api/wiki/Rustdesk-Command))
|
||||
- Web Client
|
||||
- Automatically obtain API server
|
||||
- Automatically obtain ID server and KEY
|
||||
- Automatically obtain address book
|
||||
- Visitors are remotely to the device via a temporary sharing link
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### [Rustdesk](https://github.com/rustdesk/rustdesk)
|
||||
|
||||
|
||||
#### The PC client uses version ***1.3.0***, and versions ***1.2.6+*** have been tested to work.
|
||||
|
||||
#### Solutions for PC client connection timeout or connection issues
|
||||
##### Connection issues or timeouts
|
||||
Because the server version lags behind the client version, the server does not respond to the client's `secure_tcp` request, causing the client to timeout.
|
||||
Relevant code can be found at `https://github.com/rustdesk/rustdesk/blob/master/src/client.rs#L322`
|
||||
```rust
|
||||
if !key.is_empty() && !token.is_empty() {
|
||||
// mainly for the security of token
|
||||
allow_err!(secure_tcp(&mut socket, key).await);
|
||||
}
|
||||
```
|
||||
|
||||
As seen, when both `key` and `token` are not empty, `secure_tcp` is called, but the server does not respond, causing the client to timeout.
|
||||
The `secure_tcp` code is located at `https://github.com/rustdesk/rustdesk/blob/master/src/common.rs#L1203`
|
||||
|
||||
##### Four Solutions
|
||||
1. Specify the key on the server.
|
||||
- Advantage: Simple
|
||||
- Disadvantage: The connection is not encrypted
|
||||
```bash
|
||||
hbbs -r <relay-server-ip[:port]> -k <key>
|
||||
hbbr -k <key>
|
||||
```
|
||||
For example
|
||||
```bash
|
||||
hbbs -r <relay-server-ip[:port]> -k abc1234567
|
||||
hbbr -k abc1234567
|
||||
```
|
||||
2. Use a system-generated key or a custom key pair on the server. If the client is already logged in, it may timeout or fail to connect. Logging out and reconnecting usually resolves the issue, and the web client does not need to log out.
|
||||
- Advantage: Encrypted connection
|
||||
- Disadvantage: Complicated operation
|
||||
3. Use a system-generated key or a custom key pair on the server, fork the official client code to modify `secure_tcp` to return directly, then compile using `Github Actions` and download the compiled client.
|
||||
Refer to [official documentation](https://rustdesk.com/docs/en/dev/build/all/)
|
||||
- Advantage: Encrypted connection, customizable client features, ready to use after compilation
|
||||
- Disadvantage: Requires forking code and compiling, which can be challenging
|
||||
4. Use [my forked code](https://github.com/lejianwen/rustdesk), which has already modified `secure_tcp`. You can download and use it directly from [here](https://github.com/lejianwen/rustdesk/releases)
|
||||
- Advantage: Code changes are viewable, compiled with `Github Actions`, encrypted connection, ready to use
|
||||
- Disadvantage: May not keep up with official version updates
|
||||
|
||||
***If encryption is not a high priority, use `1`. If encryption is important, use `3` or `4`.***
|
||||
- CLI
|
||||
- Reset admin password
|
||||
|
||||
## Overview
|
||||
|
||||
### API Service
|
||||
Basic implementation of the PC client's primary interfaces.Supports the Personal version api, which can be enabled by configuring the `rustdesk.personal` file or the `RUSTDESK_API_RUSTDESK_PERSONAL` environment variable.
|
||||
|
||||
#### Login
|
||||
|
||||
- Added `GitHub`, `Google` and `OIDC` login, which can be used after configuration in the admin panel. See the OAuth
|
||||
configuration section for details.
|
||||
- Added authorization login for the web admin panel.
|
||||
|
||||

|
||||
|
||||
#### Address Book
|
||||
|
||||

|
||||
|
||||
#### Groups
|
||||
Groups are divided into `shared groups` and `regular groups`. In shared groups, everyone can see the peers of all group members, while in regular groups, only administrators can see all members' peers.
|
||||
|
||||

|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%" align="center" colspan="2"><b>Login</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" align="center" colspan="2"><img src="docs/en_img/pc_login.png"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" align="center"><b>Address Book</b></td>
|
||||
<td width="50%" align="center"><b>Groups</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" align="center"><img src="docs/en_img/pc_ab.png"></td>
|
||||
<td width="50%" align="center"><img src="docs/en_img/pc_gr.png"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Web Admin
|
||||
|
||||
@@ -123,12 +79,11 @@ installation are `admin` `admin`, please change the password immediately.
|
||||
In the top right corner, you can change the password, switch languages, and toggle between `day/night` mode.
|
||||
|
||||

|
||||
3. Groups can be customized for easy management. Currently, two types are supported: `shared group` and `regular group`.
|
||||

|
||||
4. You can directly launch the client or open the web client for convenience; you can also share it with guests, who can remotely access the device via the web client.
|
||||
|
||||
3. Each user can have multiple address books, which can also be shared with other users.
|
||||
4. Groups can be customized for easy management. Currently, two types are supported: `shared group` and `regular group`.
|
||||
5. You can directly launch the client or open the web client for convenience; you can also share it with guests, who can remotely access the device via the web client.
|
||||

|
||||
5. OAuth support: Currently, `GitHub`, `Google` and `OIDC` are supported. You need to create an `OAuth App` and configure it in
|
||||
6. OAuth support: Currently, `GitHub`, `Google` and `OIDC` are supported. You need to create an `OAuth App` and configure it in
|
||||
the admin panel.
|
||||

|
||||
- For `Google` and `Github`, you don't need to fill the `Issuer` and `Scpoes`
|
||||
@@ -137,15 +92,34 @@ installation are `admin` `admin`, please change the password immediately.
|
||||
at `Settings` -> `Developer settings` -> `OAuth Apps` -> `New OAuth App` [here](https://github.com/settings/developers).
|
||||
- Set the `Authorization callback URL` to `http://<your server[:port]>/api/oauth/callback`,
|
||||
e.g., `http://127.0.0.1:21114/api/oauth/callback`.
|
||||
|
||||
7. Login logs
|
||||
8. Connection logs
|
||||
9. File transfer logs
|
||||
10. Server control
|
||||
|
||||
- `Simple mode`, some simple commands have been GUI-ized and can be executed directly in the backend
|
||||

|
||||
|
||||
- `Advanced mode`, commands can be executed directly in the backend
|
||||
* Official commands can be used
|
||||
* Custom commands can be added
|
||||
* Custom commands can be executed
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### Web Client:
|
||||
|
||||
1. If you're already logged into the admin panel, the web client will log in automatically.
|
||||
2. If you're not logged in, simply click the login button in the top right corner, and the API server will be
|
||||
pre-configured.
|
||||

|
||||
3. After logging in, the ID server and key will be automatically synced.
|
||||
4. The address book will also be automatically saved to the web client for convenient use.
|
||||
5. Now supports `v2 Preview`, accessible at `/webclient2`
|
||||

|
||||
6. `v2 preview` deployment, [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
|
||||
|
||||
### Automated Documentation : API documentation is generated using Swag, making it easier for developers to understand and use the API.
|
||||
|
||||
@@ -153,6 +127,17 @@ installation are `admin` `admin`, please change the password immediately.
|
||||
2. PC client docs: `<your server[:port]>/swagger/index.html`
|
||||

|
||||
|
||||
### CLI
|
||||
```bash
|
||||
# help
|
||||
./apimain -h
|
||||
```
|
||||
|
||||
#### Reset admin password
|
||||
```bash
|
||||
./apimain reset-admin-pwd <pwd>
|
||||
```
|
||||
|
||||
## Installation and Setup
|
||||
|
||||
### Configuration
|
||||
@@ -165,6 +150,8 @@ installation are `admin` `admin`, please change the password immediately.
|
||||
lang: "en"
|
||||
app:
|
||||
web-client: 1 # web client route 1:open 0:close
|
||||
register: false #register enable
|
||||
show-swagger: 0 #show swagger 1:open 0:close
|
||||
gin:
|
||||
api-addr: "0.0.0.0:21114"
|
||||
mode: "release"
|
||||
@@ -192,36 +179,51 @@ logger:
|
||||
proxy:
|
||||
enable: false
|
||||
host: ""
|
||||
jwt:
|
||||
key: ""
|
||||
expire-duration: 360000
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
The prefix for variable names is `RUSTDESK_API`. If environment variables exist, they will override the configurations in the configuration file.
|
||||
|
||||
| Variable Name | Description | Example |
|
||||
|------------------------------------|-----------------------------------------------------------|-------------------------------|
|
||||
| TZ | timezone | Asia/Shanghai |
|
||||
| RUSTDESK_API_LANG | Language | `en`,`zh-CN` |
|
||||
| RUSTDESK_API_APP_WEB_CLIENT | web client on/off; 1: on, 0 off, deault 1 | 1 |
|
||||
| ----- GIN Configuration ----- | --------------------------------------- | ----------------------------- |
|
||||
| RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 |
|
||||
| ----- GORM Configuration ----- | --------------------------------------- | ----------------------------- |
|
||||
| RUSTDESK_API_GORM_TYPE | Database type (`sqlite` or `mysql`). Default is `sqlite`. | sqlite |
|
||||
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | Maximum idle connections | 10 |
|
||||
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | Maximum open connections | 100 |
|
||||
| RUSTDESK_API_RUSTDESK_PERSONAL | Open Personal Api 1:Enable,0:Disable | 1 |
|
||||
| ----- MYSQL Configuration ----- | --------------------------------------- | ----------------------------- |
|
||||
| RUSTDESK_API_MYSQL_USERNAME | MySQL username | root |
|
||||
| RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 |
|
||||
| RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 |
|
||||
| RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk |
|
||||
| ----- RUSTDESK Configuration ----- | --------------------------------------- | ----------------------------- |
|
||||
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk ID server address | 192.168.1.66:21116 |
|
||||
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk relay server address | 192.168.1.66:21117 |
|
||||
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 |
|
||||
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 |
|
||||
| ---- PROXY ----- | --------------- | ---------- |
|
||||
| RUSTDESK_API_PROXY_ENABLE | proxy_enable :`false`, `true` | `false` |
|
||||
| RUSTDESK_API_PROXY_HOST | proxy_host | `http://127.0.0.1:1080` |
|
||||
| Variable Name | Description | Example |
|
||||
|---------------------------------------------------|--------------------------------------------------------------------------------------------------------------|-------------------------------|
|
||||
| TZ | timezone | Asia/Shanghai |
|
||||
| RUSTDESK_API_LANG | Language | `en`,`zh-CN` |
|
||||
| RUSTDESK_API_APP_WEB_CLIENT | web client on/off; 1: on, 0 off, default: 1 | 1 |
|
||||
| RUSTDESK_API_APP_REGISTER | register enable; `true`, `false`; default:`false` | `false` |
|
||||
| RUSTDESK_API_APP_SHOW_SWAGGER | swagger visible; 1: yes, 0: no; default: 0 | `0` |
|
||||
| RUSTDESK_API_APP_TOKEN_EXPIRE | token expire duration(second) | `3600` |
|
||||
| ----- ADMIN Configuration----- | ---------- | ---------- |
|
||||
| RUSTDESK_API_ADMIN_TITLE | Admin Title | `RustDesk Api Admin` |
|
||||
| RUSTDESK_API_ADMIN_HELLO | Admin welcome message, you can use `html` | |
|
||||
| RUSTDESK_API_ADMIN_HELLO_FILE | Admin welcome message file,<br>will override `RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` |
|
||||
| ----- GIN Configuration ----- | --------------------------------------- | ----------------------------- |
|
||||
| RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 |
|
||||
| ----- GORM Configuration ----- | --------------------------------------- | ----------------------------- |
|
||||
| RUSTDESK_API_GORM_TYPE | Database type (`sqlite` or `mysql`). Default is `sqlite`. | sqlite |
|
||||
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | Maximum idle connections | 10 |
|
||||
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | Maximum open connections | 100 |
|
||||
| RUSTDESK_API_RUSTDESK_PERSONAL | Open Personal Api 1:Enable,0:Disable | 1 |
|
||||
| ----- MYSQL Configuration ----- | --------------------------------------- | ----------------------------- |
|
||||
| RUSTDESK_API_MYSQL_USERNAME | MySQL username | root |
|
||||
| RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 |
|
||||
| RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 |
|
||||
| RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk |
|
||||
| ----- RUSTDESK Configuration ----- | --------------------------------------- | ----------------------------- |
|
||||
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk ID server address | 192.168.1.66:21116 |
|
||||
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk relay server address | 192.168.1.66:21117 |
|
||||
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 |
|
||||
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 |
|
||||
| RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk key file | `./conf/data/id_ed25519.pub` |
|
||||
| RUSTDESK_API_RUSTDESK_WEBCLIENT_MAGIC_QUERYONLINE | New online query method is enabled in the web client v2; '1': Enabled, '0': Disabled, not enabled by default | `0` |
|
||||
| ---- PROXY ----- | --------------- | ---------- |
|
||||
| RUSTDESK_API_PROXY_ENABLE | proxy_enable :`false`, `true` | `false` |
|
||||
| RUSTDESK_API_PROXY_HOST | proxy_host | `http://127.0.0.1:1080` |
|
||||
| ----JWT---- | -------- | -------- |
|
||||
| RUSTDESK_API_JWT_KEY | JWT KEY. Set empty to disable jwt | |
|
||||
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT expire duration | 360000 |
|
||||
|
||||
### Installation Steps
|
||||
|
||||
@@ -241,189 +243,7 @@ The prefix for variable names is `RUSTDESK_API`. If environment variables exist,
|
||||
lejianwen/rustdesk-api
|
||||
```
|
||||
|
||||
2. Using `docker-compose`
|
||||
- Simple example:
|
||||
```yaml
|
||||
services:
|
||||
rustdesk-api:
|
||||
container_name: rustdesk-api
|
||||
environment:
|
||||
- RUSTDESK_API_LANG=en
|
||||
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
|
||||
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
|
||||
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
|
||||
- RUSTDESK_API_RUSTDESK_KEY=<key>
|
||||
ports:
|
||||
- 21114:21114
|
||||
image: lejianwen/rustdesk-api
|
||||
volumes:
|
||||
- /data/rustdesk/api:/app/data # Mount the database for easy backup
|
||||
networks:
|
||||
- rustdesk-net
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
- Example with RustDesk's official Docker Compose file, adding your `rustdesk-api` service:
|
||||
- If you are using a system-generated KEY, remove the `-k <key>` parameter. However, after the first startup, run `docker-compose logs hbbs` or `cat ./data/id_ed25519.pub` to view the KEY, then modify `RUSTDESK_API_RUSTDESK_KEY=<key>` and execute `docker-compose up -d` again.
|
||||
```yaml
|
||||
networks:
|
||||
rustdesk-net:
|
||||
external: false
|
||||
services:
|
||||
hbbs:
|
||||
container_name: hbbs
|
||||
ports:
|
||||
- 21115:21115
|
||||
- 21116:21116 # 自定义 hbbs 映射端口
|
||||
- 21116:21116/udp # 自定义 hbbs 映射端口
|
||||
- 21118:21118 # web client
|
||||
image: rustdesk/rustdesk-server
|
||||
command: hbbs -r <relay-server-ip[:port]> -k <key> # 填入个人域名或 IP + hbbr 暴露端口
|
||||
volumes:
|
||||
- ./data:/root # 自定义挂载目录
|
||||
networks:
|
||||
- rustdesk-net
|
||||
depends_on:
|
||||
- hbbr
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 64M
|
||||
hbbr:
|
||||
container_name: hbbr
|
||||
ports:
|
||||
- 21117:21117 # 自定义 hbbr 映射端口
|
||||
- 21119:21119 # web client
|
||||
image: rustdesk/rustdesk-server
|
||||
command: hbbr -k <key>
|
||||
volumes:
|
||||
- ./data:/root
|
||||
networks:
|
||||
- rustdesk-net
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 64M
|
||||
rustdesk-api:
|
||||
container_name: rustdesk-api
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
|
||||
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
|
||||
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
|
||||
- RUSTDESK_API_RUSTDESK_KEY=<key>
|
||||
ports:
|
||||
- 21114:21114
|
||||
image: lejianwen/rustdesk-api
|
||||
volumes:
|
||||
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
|
||||
networks:
|
||||
- rustdesk-net
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
- S6 image
|
||||
- - If using ***custom KEY***, you will need to modify the startup script to override the `/etc/s6-overlay/s6-rc.d/hbbr/run` and `/etc/s6-overlay/s6-rc.d/hbbr/run` in the image.
|
||||
1. Create `hbbr/run`, only needed for custom KEY
|
||||
```bash
|
||||
#!/command/with-contenv sh
|
||||
cd /data
|
||||
PARAMS=
|
||||
[ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k ${KEY}"
|
||||
/usr/bin/hbbr $PARAMS
|
||||
```
|
||||
2. Create `hbbs/run`, only needed for custom KEY
|
||||
```bash
|
||||
#!/command/with-contenv sh
|
||||
sleep 2
|
||||
cd /data
|
||||
PARAMS=
|
||||
[ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k ${KEY}"
|
||||
/usr/bin/hbbs -r $RELAY $PARAMS
|
||||
```
|
||||
3. Modify the `s6` section in `docker-compose.yml`
|
||||
```yaml
|
||||
networks:
|
||||
rustdesk-net:
|
||||
external: false
|
||||
services:
|
||||
rustdesk-server:
|
||||
container_name: rustdesk-server
|
||||
ports:
|
||||
- 21115:21115
|
||||
- 21116:21116
|
||||
- 21116:21116/udp
|
||||
- 21117:21117
|
||||
- 21118:21118
|
||||
- 21119:21119
|
||||
image: rustdesk/rustdesk-server-s6:latest
|
||||
environment:
|
||||
- RELAY=192.168.1.66:21117
|
||||
- ENCRYPTED_ONLY=1
|
||||
- KEY=<key> #KEY
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- ./hbbr/run:/etc/s6-overlay/s6-rc.d/hbbr/run
|
||||
- ./hbbs/run:/etc/s6-overlay/s6-rc.d/hbbs/run
|
||||
restart: unless-stopped
|
||||
rustdesk-api:
|
||||
container_name: rustdesk-api
|
||||
ports:
|
||||
- 21114:21114
|
||||
image: lejianwen/rustdesk-api
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
|
||||
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
|
||||
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
|
||||
- RUSTDESK_API_RUSTDESK_KEY=<key>
|
||||
volumes:
|
||||
- /data/rustdesk/api:/app/data
|
||||
networks:
|
||||
- rustdesk-net
|
||||
restart: unless-stopped
|
||||
```
|
||||
- If using ***system-generated KEY*** or ***custom KEY_PUB, KEY_PRIV***, you do not need to modify the startup script, but you need to obtain the KEY after it is generated and then run `docker-compose up -d`
|
||||
```yaml
|
||||
networks:
|
||||
rustdesk-net:
|
||||
external: false
|
||||
services:
|
||||
rustdesk-server:
|
||||
container_name: rustdesk-server
|
||||
ports:
|
||||
- 21115:21115
|
||||
- 21116:21116
|
||||
- 21116:21116/udp
|
||||
- 21117:21117
|
||||
- 21118:21118
|
||||
- 21119:21119
|
||||
image: rustdesk/rustdesk-server-s6:latest
|
||||
environment:
|
||||
- RELAY=192.168.1.66:21117
|
||||
- ENCRYPTED_ONLY=1
|
||||
volumes:
|
||||
- ./data:/data
|
||||
restart: unless-stopped
|
||||
rustdesk-api:
|
||||
container_name: rustdesk-api
|
||||
ports:
|
||||
- 21114:21114
|
||||
image: lejianwen/rustdesk-api
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
|
||||
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
|
||||
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
|
||||
- RUSTDESK_API_RUSTDESK_KEY=<key>
|
||||
volumes:
|
||||
- /data/rustdesk/api:/app/data
|
||||
networks:
|
||||
- rustdesk-net
|
||||
restart: unless-stopped
|
||||
```
|
||||
2. Using `docker-compose`,look [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
|
||||
|
||||
#### Running from Release
|
||||
|
||||
@@ -472,22 +292,55 @@ Download the release from [release](https://github.com/lejianwen/rustdesk-api/re
|
||||
6. Open your browser and visit `http://<your server[:port]>/_admin/`, with default credentials `admin admin`. Please
|
||||
change the password promptly.
|
||||
|
||||
#### nginx reverse proxy
|
||||
Configure reverse proxy in `nginx`
|
||||
```
|
||||
server {
|
||||
listen <your port>;
|
||||
server_name <your server>;
|
||||
location / {
|
||||
proxy_pass http://<api-server[:port]>;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
#### Running with my forked server-s6 image
|
||||
|
||||
- github https://github.com/lejianwen/rustdesk-server
|
||||
- docker hub https://hub.docker.com/r/lejianwen/rustdesk-server-s6
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
rustdesk-net:
|
||||
external: false
|
||||
services:
|
||||
rustdesk:
|
||||
ports:
|
||||
- 21114:21114
|
||||
- 21115:21115
|
||||
- 21116:21116
|
||||
- 21116:21116/udp
|
||||
- 21117:21117
|
||||
- 21118:21118
|
||||
- 21119:21119
|
||||
image: lejianwen/rustdesk-server-s6:latest
|
||||
environment:
|
||||
- RELAY=<relay_server[:port]>
|
||||
- ENCRYPTED_ONLY=1
|
||||
- MUST_LOGIN=N
|
||||
- TZ=Asia/Shanghai
|
||||
- RUSTDESK_API_RUSTDESK_ID_SERVER=<id_server[:21116]>
|
||||
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=<relay_server[:21117]>
|
||||
- RUSTDESK_API_RUSTDESK_API_SERVER=http://<api_server[:21114]>
|
||||
- RUSTDESK_API_KEY_FILE=/data/id_ed25519.pub
|
||||
- RUSTDESK_API_JWT_KEY=xxxxxx # jwt key
|
||||
volumes:
|
||||
- /data/rustdesk/server:/data
|
||||
- /data/rustdesk/api:/app/data #将数据库挂载
|
||||
networks:
|
||||
- rustdesk-net
|
||||
restart: unless-stopped
|
||||
|
||||
```
|
||||
## Others
|
||||
|
||||
- [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
|
||||
- [Connection Timeout](https://github.com/lejianwen/rustdesk-api/issues/92)
|
||||
- [Change client ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)
|
||||
- [Web client source](https://hub.docker.com/r/keyurbhole/flutter_web_desk)
|
||||
- [Web client source](https://hub.docker.com/r/keyurbhole/flutter_web_desk)
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Thanks to everyone who contributed!
|
||||
|
||||
<a href="https://github.com/lejianwen/rustdesk-api/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" />
|
||||
</a>
|
||||
|
||||
102
cmd/apimain.go
@@ -5,6 +5,7 @@ import (
|
||||
"Gwen/global"
|
||||
"Gwen/http"
|
||||
"Gwen/lib/cache"
|
||||
"Gwen/lib/jwt"
|
||||
"Gwen/lib/lock"
|
||||
"Gwen/lib/logger"
|
||||
"Gwen/lib/orm"
|
||||
@@ -14,6 +15,10 @@ import (
|
||||
"fmt"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// @title 管理系统API
|
||||
@@ -26,9 +31,76 @@ import (
|
||||
// @securitydefinitions.apikey BearerAuth
|
||||
// @in header
|
||||
// @name Authorization
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "apimain",
|
||||
Short: "RUSTDESK API SERVER",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
InitGlobal()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
global.Logger.Info("API SERVER START")
|
||||
http.ApiInit()
|
||||
},
|
||||
}
|
||||
|
||||
var resetPwdCmd = &cobra.Command{
|
||||
Use: "reset-admin-pwd [pwd]",
|
||||
Example: "reset-admin-pwd 123456",
|
||||
Short: "Reset Admin Password",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
pwd := args[0]
|
||||
admin := service.AllService.UserService.InfoById(1)
|
||||
err := service.AllService.UserService.UpdatePassword(admin, pwd)
|
||||
if err != nil {
|
||||
fmt.Printf("reset password fail! %v \n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("reset password success! \n")
|
||||
},
|
||||
}
|
||||
var resetUserPwdCmd = &cobra.Command{
|
||||
Use: "reset-pwd [userId] [pwd]",
|
||||
Example: "reset-pwd 2 123456",
|
||||
Short: "Reset User Password",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
userId := args[0]
|
||||
pwd := args[1]
|
||||
uid, err := strconv.Atoi(userId)
|
||||
if err != nil {
|
||||
fmt.Printf("userId must be int! \n")
|
||||
return
|
||||
}
|
||||
if uid <= 0 {
|
||||
fmt.Printf("userId must be greater than 0! \n")
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.InfoById(uint(uid))
|
||||
err = service.AllService.UserService.UpdatePassword(u, pwd)
|
||||
if err != nil {
|
||||
fmt.Printf("reset password fail! %v \n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("reset password success! \n")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&global.ConfigPath, "config", "c", "./conf/config.yaml", "choose config file")
|
||||
rootCmd.AddCommand(resetPwdCmd, resetUserPwdCmd)
|
||||
}
|
||||
func main() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func InitGlobal() {
|
||||
//配置解析
|
||||
global.Viper = config.Init(&global.Config)
|
||||
global.Viper = config.Init(&global.Config, global.ConfigPath)
|
||||
|
||||
//日志
|
||||
global.Logger = logger.New(&logger.Config{
|
||||
@@ -90,18 +162,13 @@ func main() {
|
||||
|
||||
//jwt
|
||||
//fmt.Println(global.Config.Jwt.PrivateKey)
|
||||
//global.Jwt = jwt.NewJwt(global.Config.Jwt.PrivateKey, global.Config.Jwt.ExpireDuration*time.Second)
|
||||
global.Jwt = jwt.NewJwt(global.Config.Jwt.Key, global.Config.Jwt.ExpireDuration*time.Second)
|
||||
|
||||
//locker
|
||||
global.Lock = lock.NewLocal()
|
||||
|
||||
//gin
|
||||
http.ApiInit()
|
||||
|
||||
}
|
||||
|
||||
func DatabaseAutoUpdate() {
|
||||
version := 243
|
||||
version := 260
|
||||
|
||||
db := global.DB
|
||||
|
||||
@@ -146,6 +213,24 @@ func DatabaseAutoUpdate() {
|
||||
if v.Version < uint(version) {
|
||||
Migrate(uint(version))
|
||||
}
|
||||
// 245迁移
|
||||
if v.Version < 245 {
|
||||
//oauths 表的 oauth_type 字段设置为 op同样的值
|
||||
db.Exec("update oauths set oauth_type = op")
|
||||
db.Exec("update oauths set issuer = 'https://accounts.google.com' where op = 'google'")
|
||||
db.Exec("update user_thirds set oauth_type = third_type, op = third_type")
|
||||
//通过email迁移旧的google授权
|
||||
uts := make([]model.UserThird, 0)
|
||||
db.Where("oauth_type = ?", "google").Find(&uts)
|
||||
for _, ut := range uts {
|
||||
if ut.UserId > 0 {
|
||||
db.Model(&model.User{}).Where("id = ?", ut.UserId).Update("email", ut.OpenId)
|
||||
}
|
||||
}
|
||||
}
|
||||
if v.Version < 246 {
|
||||
db.Exec("update oauths set issuer = 'https://accounts.google.com' where op = 'google' and issuer is null")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -167,6 +252,7 @@ func Migrate(version uint) {
|
||||
&model.AuditFile{},
|
||||
&model.AddressBookCollection{},
|
||||
&model.AddressBookCollectionRule{},
|
||||
&model.ServerCmd{},
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Println("migrate err :=>", err)
|
||||
|
||||
1
conf/admin/hello.html
Normal file
@@ -0,0 +1 @@
|
||||
### 👏👏👏 你好 ***{{username}}***, 欢迎使用 [RustDesk Api](https://github.com/lejianwen/rustdesk-api)
|
||||
@@ -1,6 +1,13 @@
|
||||
lang: "zh-CN"
|
||||
app:
|
||||
web-client: 1 # 1:启用 0:禁用
|
||||
register: false #是否开启注册
|
||||
show-swagger: 0 # 1:启用 0:禁用
|
||||
token-expire: 360000
|
||||
admin:
|
||||
title: "RustDesk Api Admin"
|
||||
hello-file: "./conf/admin/hello.html" #优先使用file
|
||||
hello: ""
|
||||
gin:
|
||||
api-addr: "0.0.0.0:21114"
|
||||
mode: "release" #release,debug,test
|
||||
@@ -19,15 +26,20 @@ rustdesk:
|
||||
id-server: "192.168.1.66:21116"
|
||||
relay-server: "192.168.1.66:21117"
|
||||
api-server: "http://127.0.0.1:21114"
|
||||
key: "123456789"
|
||||
key: ""
|
||||
key-file: "/data/id_ed25519.pub"
|
||||
personal: 1
|
||||
webclient-magic-queryonline: 0
|
||||
logger:
|
||||
path: "./runtime/log.txt"
|
||||
level: "warn" #trace,debug,info,warn,error,fatal
|
||||
level: "info" #trace,debug,info,warn,error,fatal
|
||||
report-caller: true
|
||||
proxy:
|
||||
enable: false
|
||||
host: "http://127.0.0.1:1080"
|
||||
jwt:
|
||||
key: ""
|
||||
expire-duration: 360000
|
||||
redis:
|
||||
addr: "127.0.0.1:6379"
|
||||
password: ""
|
||||
@@ -45,6 +57,4 @@ oss:
|
||||
callback-url: ""
|
||||
expire-time: 30
|
||||
max-byte: 10240
|
||||
jwt:
|
||||
private-key: "./conf/jwt_pri.pem"
|
||||
expire-duration: 360000
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/spf13/viper"
|
||||
@@ -15,12 +14,20 @@ const (
|
||||
)
|
||||
|
||||
type App struct {
|
||||
WebClient int `mapstructure:"web-client"`
|
||||
WebClient int `mapstructure:"web-client"`
|
||||
Register bool `mapstructure:"register"`
|
||||
ShowSwagger int `mapstructure:"show-swagger"`
|
||||
TokenExpire int `mapstructure:"token-expire"`
|
||||
}
|
||||
type Admin struct {
|
||||
Title string `mapstructure:"title"`
|
||||
Hello string `mapstructure:"hello"`
|
||||
HelloFile string `mapstructure:"hello-file"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Lang string `mapstructure:"lang"`
|
||||
App App
|
||||
Admin Admin
|
||||
Gorm Gorm
|
||||
Mysql Mysql
|
||||
Gin Gin
|
||||
@@ -34,18 +41,15 @@ type Config struct {
|
||||
}
|
||||
|
||||
// Init 初始化配置
|
||||
func Init(rowVal interface{}) *viper.Viper {
|
||||
var config string
|
||||
flag.StringVar(&config, "c", "", "choose config file.")
|
||||
flag.Parse()
|
||||
if config == "" { // 优先级: 命令行 > 默认值
|
||||
config = DefaultConfig
|
||||
func Init(rowVal *Config, path string) *viper.Viper {
|
||||
if path == "" {
|
||||
path = DefaultConfig
|
||||
}
|
||||
v := viper.New()
|
||||
v := viper.GetViper()
|
||||
v.AutomaticEnv()
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
|
||||
v.SetEnvPrefix("RUSTDESK_API")
|
||||
v.SetConfigFile(config)
|
||||
v.SetConfigFile(path)
|
||||
v.SetConfigType("yaml")
|
||||
err := v.ReadInConfig()
|
||||
if err != nil {
|
||||
@@ -58,10 +62,14 @@ func Init(rowVal interface{}) *viper.Viper {
|
||||
if err2 := v.Unmarshal(rowVal); err2 != nil {
|
||||
fmt.Println(err2)
|
||||
}
|
||||
rowVal.Rustdesk.LoadKeyFile()
|
||||
rowVal.Rustdesk.ParsePort()
|
||||
})
|
||||
if err := v.Unmarshal(rowVal); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
rowVal.Rustdesk.LoadKeyFile()
|
||||
rowVal.Rustdesk.ParsePort()
|
||||
return v
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@ package config
|
||||
import "time"
|
||||
|
||||
type Jwt struct {
|
||||
PrivateKey string `mapstructure:"private-key"`
|
||||
Key string `mapstructure:"key"`
|
||||
ExpireDuration time.Duration `mapstructure:"expire-duration"`
|
||||
}
|
||||
|
||||
@@ -1,9 +1,57 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultIdServerPort = 21116
|
||||
DefaultRelayServerPort = 21117
|
||||
)
|
||||
|
||||
type Rustdesk struct {
|
||||
IdServer string `mapstructure:"id-server"`
|
||||
RelayServer string `mapstructure:"relay-server"`
|
||||
ApiServer string `mapstructure:"api-server"`
|
||||
Key string `mapstructure:"key"`
|
||||
Personal int `mapstructure:"personal"`
|
||||
IdServer string `mapstructure:"id-server"`
|
||||
IdServerPort int `mapstructure:"-"`
|
||||
RelayServer string `mapstructure:"relay-server"`
|
||||
RelayServerPort int `mapstructure:"-"`
|
||||
ApiServer string `mapstructure:"api-server"`
|
||||
Key string `mapstructure:"key"`
|
||||
KeyFile string `mapstructure:"key-file"`
|
||||
Personal int `mapstructure:"personal"`
|
||||
//webclient-magic-queryonline
|
||||
WebclientMagicQueryonline int `mapstructure:"webclient-magic-queryonline"`
|
||||
}
|
||||
|
||||
func (rd *Rustdesk) LoadKeyFile() {
|
||||
// Load key file
|
||||
if rd.Key != "" {
|
||||
return
|
||||
}
|
||||
if rd.KeyFile != "" {
|
||||
// Load key from file
|
||||
b, err := os.ReadFile(rd.KeyFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rd.Key = string(b)
|
||||
return
|
||||
}
|
||||
}
|
||||
func (rd *Rustdesk) ParsePort() {
|
||||
// Parse port
|
||||
idres := strings.Split(rd.IdServer, ":")
|
||||
if len(idres) == 1 {
|
||||
rd.IdServerPort = DefaultIdServerPort
|
||||
} else if len(idres) == 2 {
|
||||
rd.IdServerPort, _ = strconv.Atoi(idres[1])
|
||||
}
|
||||
|
||||
relayres := strings.Split(rd.RelayServer, ":")
|
||||
if len(relayres) == 1 {
|
||||
rd.RelayServerPort = DefaultRelayServerPort
|
||||
} else if len(relayres) == 2 {
|
||||
rd.RelayServerPort, _ = strconv.Atoi(relayres[1])
|
||||
}
|
||||
}
|
||||
|
||||
5
debian/changelog
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
rustdesk-api-server (1.3.6) UNRELEASED; urgency=medium
|
||||
|
||||
* Update the version to 1.3.6 to match the client.
|
||||
|
||||
-- rustdesk-api <ymwlpoolc@qq.com> Tue, 24 Dec 2024 13:48:34 +0800
|
||||
1
debian/compat
vendored
Normal file
@@ -0,0 +1 @@
|
||||
10
|
||||
13
debian/control.tpl
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
Source: rustdesk-api-server
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: ymwl <ymwlpoolc@qq.com>
|
||||
Build-Depends: debhelper (>= 10), pkg-config
|
||||
Standards-Version: 4.5.0
|
||||
Homepage: https://github.com/lejianwen/rustdesk-api/
|
||||
|
||||
Package: rustdesk-api-server
|
||||
Architecture: {{ ARCH }}
|
||||
Depends: systemd ${misc:Depends}
|
||||
Description: RustDesk api server
|
||||
RustDesk api server, it is free and open source.
|
||||
21
debian/copyright
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024-present Lejianwen and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
6
debian/rules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/make -f
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_builddeb:
|
||||
dh_builddeb -- -Zgzip
|
||||
6
debian/rustdesk-api-server.install
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
bin/rustdesk-api usr/bin
|
||||
systemd/rustdesk-api.service lib/systemd/system
|
||||
conf var/lib/rustdesk-api
|
||||
data var/lib/rustdesk-api
|
||||
resources var/lib/rustdesk-api
|
||||
runtime var/lib/rustdesk-api
|
||||
28
debian/rustdesk-api-server.postinst
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
SERVICE=rustdesk-api.service
|
||||
|
||||
if [ "$1" = "configure" ]; then
|
||||
mkdir -p /var/log/rustdesk-api
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
configure|abort-upgrade|abort-deconfigure|abort-remove)
|
||||
mkdir -p /var/lib/rustdesk-api/
|
||||
deb-systemd-helper unmask "${SERVICE}" >/dev/null || true
|
||||
if deb-systemd-helper --quiet was-enabled "${SERVICE}"; then
|
||||
deb-systemd-invoke enable "${SERVICE}" >/dev/null || true
|
||||
else
|
||||
deb-systemd-invoke update-state "${SERVICE}" >/dev/null || true
|
||||
fi
|
||||
systemctl --system daemon-reload >/dev/null || true
|
||||
if [ -n "$2" ]; then
|
||||
deb-systemd-invoke restart "${SERVICE}" >/dev/null || true
|
||||
else
|
||||
deb-systemd-invoke start "${SERVICE}" >/dev/null || true
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
18
debian/rustdesk-api-server.postrm
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
SERVICE=rustdesk-api.service
|
||||
|
||||
systemctl --system daemon-reload >/dev/null || true
|
||||
|
||||
if [ "$1" = "purge" ]; then
|
||||
rm -rf /var/log/rustdesk-api/rustdesk-api.*
|
||||
deb-systemd-helper purge "${SERVICE}" >/dev/null || true
|
||||
deb-systemd-helper unmask "${SERVICE}" >/dev/null || true
|
||||
fi
|
||||
|
||||
if [ "$1" = "remove" ]; then
|
||||
deb-systemd-helper mask "${SERVICE}" >/dev/null || true
|
||||
fi
|
||||
|
||||
exit 0
|
||||
13
debian/rustdesk-api-server.prerm
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
SERVICE=rustdesk-api.service
|
||||
|
||||
case "$1" in
|
||||
remove|deconfigure)
|
||||
deb-systemd-invoke stop "${SERVICE}" >/dev/null || true
|
||||
deb-systemd-invoke disable "${SERVICE}" >/dev/null || true
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
1
debian/source/format
vendored
Normal file
@@ -0,0 +1 @@
|
||||
3.0 (native)
|
||||
@@ -3,6 +3,10 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
args:
|
||||
COUNTRY: CN
|
||||
FREONTEND_GIT_REPO: https://github.com/lejianwen/rustdesk-api-web.git
|
||||
FRONTEND_GIT_BRANCH: master
|
||||
# image: lejianwen/rustdesk-api
|
||||
container_name: rustdesk-api
|
||||
environment:
|
||||
|
||||
35
docker-dev.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Define Docker Compose file and cache option
|
||||
COMPOSE_FILE_NAME="docker-compose-dev.yaml"
|
||||
CACHE=""
|
||||
# Uncomment the next line to enable no-cache option
|
||||
# CACHE="--no-cache"
|
||||
|
||||
# Define the base Docker Compose command
|
||||
DCS="docker compose -f ${COMPOSE_FILE_NAME}"
|
||||
|
||||
# Function to build and start services
|
||||
build_and_run() {
|
||||
echo "Building services..."
|
||||
if ! $DCS build ${CACHE}; then
|
||||
echo "Error: Failed to build services"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Starting services..."
|
||||
if ! $DCS up -d; then
|
||||
echo "Error: Failed to start services"
|
||||
exit 1
|
||||
fi
|
||||
echo "Services started successfully"
|
||||
echo "If you want to stop the services, run"
|
||||
echo "docker compose -f ${COMPOSE_FILE_NAME} down"
|
||||
|
||||
echo "If you want to see the logs, run"
|
||||
echo "docker compose -f ${COMPOSE_FILE_NAME} logs -f"
|
||||
}
|
||||
|
||||
# Execute build and start function
|
||||
build_and_run
|
||||
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 97 KiB |
@@ -653,40 +653,6 @@ const docTemplateapi = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"token": []
|
||||
}
|
||||
],
|
||||
"description": "用户信息",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"用户"
|
||||
],
|
||||
"summary": "用户信息",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.UserPayload"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/audit/conn": {
|
||||
"post": {
|
||||
"description": "审计连接",
|
||||
@@ -767,6 +733,40 @@ const docTemplateapi = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/currentUser": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"token": []
|
||||
}
|
||||
],
|
||||
"description": "用户信息",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"用户"
|
||||
],
|
||||
"summary": "用户信息",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.UserPayload"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/heartbeat": {
|
||||
"post": {
|
||||
"description": "心跳",
|
||||
@@ -1075,6 +1075,40 @@ const docTemplateapi = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/server-config-v2": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"token": []
|
||||
}
|
||||
],
|
||||
"description": "服务配置,给webclient提供api-server",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"WEBCLIENT_V2"
|
||||
],
|
||||
"summary": "服务配置",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/shared-peer": {
|
||||
"post": {
|
||||
"description": "分享的peer",
|
||||
@@ -1144,43 +1178,6 @@ const docTemplateapi = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/tags": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "标签",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"地址"
|
||||
],
|
||||
"summary": "标签",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/model.Tag"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -1255,6 +1252,35 @@ const docTemplateapi = `{
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/version": {
|
||||
"get": {
|
||||
"description": "版本",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"首页"
|
||||
],
|
||||
"summary": "版本",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
@@ -1356,7 +1382,7 @@ const docTemplateapi = `{
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"maxLength": 20,
|
||||
"maxLength": 32,
|
||||
"minLength": 4
|
||||
},
|
||||
"type": {
|
||||
@@ -1364,8 +1390,8 @@ const docTemplateapi = `{
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"maxLength": 10,
|
||||
"minLength": 4
|
||||
"maxLength": 32,
|
||||
"minLength": 2
|
||||
},
|
||||
"uuid": {
|
||||
"type": "string"
|
||||
|
||||
@@ -646,40 +646,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"token": []
|
||||
}
|
||||
],
|
||||
"description": "用户信息",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"用户"
|
||||
],
|
||||
"summary": "用户信息",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.UserPayload"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/audit/conn": {
|
||||
"post": {
|
||||
"description": "审计连接",
|
||||
@@ -760,6 +726,40 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/currentUser": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"token": []
|
||||
}
|
||||
],
|
||||
"description": "用户信息",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"用户"
|
||||
],
|
||||
"summary": "用户信息",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.UserPayload"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/heartbeat": {
|
||||
"post": {
|
||||
"description": "心跳",
|
||||
@@ -1068,6 +1068,40 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/server-config-v2": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"token": []
|
||||
}
|
||||
],
|
||||
"description": "服务配置,给webclient提供api-server",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"WEBCLIENT_V2"
|
||||
],
|
||||
"summary": "服务配置",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/shared-peer": {
|
||||
"post": {
|
||||
"description": "分享的peer",
|
||||
@@ -1137,43 +1171,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/tags": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "标签",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"地址"
|
||||
],
|
||||
"summary": "标签",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/model.Tag"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -1248,6 +1245,35 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/version": {
|
||||
"get": {
|
||||
"description": "版本",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"首页"
|
||||
],
|
||||
"summary": "版本",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
@@ -1349,7 +1375,7 @@
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"maxLength": 20,
|
||||
"maxLength": 32,
|
||||
"minLength": 4
|
||||
},
|
||||
"type": {
|
||||
@@ -1357,8 +1383,8 @@
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"maxLength": 10,
|
||||
"minLength": 4
|
||||
"maxLength": 32,
|
||||
"minLength": 2
|
||||
},
|
||||
"uuid": {
|
||||
"type": "string"
|
||||
|
||||
@@ -62,14 +62,14 @@ definitions:
|
||||
id:
|
||||
type: string
|
||||
password:
|
||||
maxLength: 20
|
||||
maxLength: 32
|
||||
minLength: 4
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
username:
|
||||
maxLength: 10
|
||||
minLength: 4
|
||||
maxLength: 32
|
||||
minLength: 2
|
||||
type: string
|
||||
uuid:
|
||||
type: string
|
||||
@@ -598,27 +598,6 @@ paths:
|
||||
summary: 标签
|
||||
tags:
|
||||
- 地址[Personal]
|
||||
/api:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 用户信息
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/api.UserPayload'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.Response'
|
||||
security:
|
||||
- token: []
|
||||
summary: 用户信息
|
||||
tags:
|
||||
- 用户
|
||||
/audit/conn:
|
||||
post:
|
||||
consumes:
|
||||
@@ -671,6 +650,27 @@ paths:
|
||||
summary: 审计文件
|
||||
tags:
|
||||
- 审计
|
||||
/currentUser:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 用户信息
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/api.UserPayload'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.Response'
|
||||
security:
|
||||
- token: []
|
||||
summary: 用户信息
|
||||
tags:
|
||||
- 用户
|
||||
/heartbeat:
|
||||
post:
|
||||
consumes:
|
||||
@@ -870,6 +870,27 @@ paths:
|
||||
summary: 服务配置
|
||||
tags:
|
||||
- WEBCLIENT
|
||||
/server-config-v2:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 服务配置,给webclient提供api-server
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.Response'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.Response'
|
||||
security:
|
||||
- token: []
|
||||
summary: 服务配置
|
||||
tags:
|
||||
- WEBCLIENT_V2
|
||||
/shared-peer:
|
||||
post:
|
||||
consumes:
|
||||
@@ -915,29 +936,6 @@ paths:
|
||||
summary: 提交系统信息
|
||||
tags:
|
||||
- 地址
|
||||
/tags:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 标签
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/model.Tag'
|
||||
type: array
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.ErrorResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 标签
|
||||
tags:
|
||||
- 地址
|
||||
/users:
|
||||
get:
|
||||
consumes:
|
||||
@@ -983,6 +981,25 @@ paths:
|
||||
summary: 用户列表
|
||||
tags:
|
||||
- 群组
|
||||
/version:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 版本
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.Response'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.Response'
|
||||
summary: 版本
|
||||
tags:
|
||||
- 首页
|
||||
securityDefinitions:
|
||||
BearerAuth:
|
||||
in: header
|
||||
|
||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 97 KiB |
BIN
docs/en_img/rustdesk_command_advance.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
docs/en_img/rustdesk_command_simple.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 3.8 KiB |
BIN
docs/rustdesk_command_advance.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
docs/rustdesk_command_simple.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 13 KiB |
BIN
docs/webclientv2.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
@@ -3,15 +3,20 @@ package global
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/locales/en"
|
||||
"github.com/go-playground/locales/es"
|
||||
"github.com/go-playground/locales/fr"
|
||||
"github.com/go-playground/locales/ko"
|
||||
"github.com/go-playground/locales/ru"
|
||||
"github.com/go-playground/locales/zh_Hans_CN"
|
||||
|
||||
"github.com/go-playground/locales/zh_Hant"
|
||||
ut "github.com/go-playground/universal-translator"
|
||||
"github.com/go-playground/validator/v10"
|
||||
en_translations "github.com/go-playground/validator/v10/translations/en"
|
||||
es_translations "github.com/go-playground/validator/v10/translations/es"
|
||||
fr_translations "github.com/go-playground/validator/v10/translations/fr"
|
||||
ru_translations "github.com/go-playground/validator/v10/translations/ru"
|
||||
zh_translations "github.com/go-playground/validator/v10/translations/zh"
|
||||
zh_tw_translations "github.com/go-playground/validator/v10/translations/zh_tw"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
@@ -23,13 +28,19 @@ func ApiInitValidator() {
|
||||
cn := zh_Hans_CN.New()
|
||||
koT := ko.New()
|
||||
ruT := ru.New()
|
||||
esT := es.New()
|
||||
frT := fr.New()
|
||||
zhTwT := zh_Hant.New()
|
||||
|
||||
uni := ut.New(enT, cn, koT, ruT)
|
||||
uni := ut.New(enT, cn, koT, ruT, esT, frT, zhTwT)
|
||||
|
||||
enTrans, _ := uni.GetTranslator("en")
|
||||
zhTrans, _ := uni.GetTranslator("zh_Hans_CN")
|
||||
koTrans, _ := uni.GetTranslator("ko")
|
||||
ruTrans, _ := uni.GetTranslator("ru")
|
||||
esTrans, _ := uni.GetTranslator("es")
|
||||
frTrans, _ := uni.GetTranslator("fr")
|
||||
zhTwTrans, _ := uni.GetTranslator("zh_Hant")
|
||||
|
||||
err := zh_translations.RegisterDefaultTranslations(validate, zhTrans)
|
||||
if err != nil {
|
||||
@@ -49,6 +60,18 @@ func ApiInitValidator() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = es_translations.RegisterDefaultTranslations(validate, esTrans)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = fr_translations.RegisterDefaultTranslations(validate, frTrans)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = zh_tw_translations.RegisterDefaultTranslations(validate, zhTwTrans)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
validate.RegisterTagNameFunc(func(field reflect.StructField) string {
|
||||
label := field.Tag.Get("label")
|
||||
@@ -109,12 +132,25 @@ func getTranslatorForLang(lang string) ut.Translator {
|
||||
case "zh":
|
||||
trans, _ := Validator.UT.GetTranslator("zh_Hans_CN")
|
||||
return trans
|
||||
case "zh_TW":
|
||||
fallthrough
|
||||
case "zh-TW":
|
||||
fallthrough
|
||||
case "zh-tw":
|
||||
trans, _ := Validator.UT.GetTranslator("zh_Hant")
|
||||
return trans
|
||||
case "ko":
|
||||
trans, _ := Validator.UT.GetTranslator("ko")
|
||||
return trans
|
||||
case "ru":
|
||||
trans, _ := Validator.UT.GetTranslator("ru")
|
||||
return trans
|
||||
case "es":
|
||||
trans, _ := Validator.UT.GetTranslator("es")
|
||||
return trans
|
||||
case "fr":
|
||||
trans, _ := Validator.UT.GetTranslator("fr")
|
||||
return trans
|
||||
case "en":
|
||||
fallthrough
|
||||
default:
|
||||
|
||||
@@ -17,13 +17,14 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
DB *gorm.DB
|
||||
Logger *logrus.Logger
|
||||
Config config.Config
|
||||
Viper *viper.Viper
|
||||
Redis *redis.Client
|
||||
Cache cache.Handler
|
||||
Validator struct {
|
||||
DB *gorm.DB
|
||||
Logger *logrus.Logger
|
||||
ConfigPath string = ""
|
||||
Config config.Config
|
||||
Viper *viper.Viper
|
||||
Redis *redis.Client
|
||||
Cache cache.Handler
|
||||
Validator struct {
|
||||
Validate *validator.Validate
|
||||
UT *ut.UniversalTranslator
|
||||
VTrans ut.Translator
|
||||
|
||||
6
go.mod
@@ -16,6 +16,7 @@ require (
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.9.0
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
@@ -28,7 +29,6 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.5.1 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
@@ -43,7 +43,9 @@ require (
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||
github.com/goccy/go-json v0.10.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
@@ -57,6 +59,7 @@ require (
|
||||
github.com/mitchellh/mapstructure v1.4.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mojocn/base64Captcha v1.3.6 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/spf13/afero v1.6.0 // indirect
|
||||
@@ -68,6 +71,7 @@ require (
|
||||
github.com/ugorji/go/codec v1.2.9 // indirect
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/image v0.13.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"Gwen/global"
|
||||
"Gwen/http/request/admin"
|
||||
"Gwen/http/response"
|
||||
"Gwen/model"
|
||||
"Gwen/service"
|
||||
"encoding/json"
|
||||
_ "encoding/json"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
@@ -30,11 +30,6 @@ func (ct *AddressBook) Detail(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
iid, _ := strconv.Atoi(id)
|
||||
t := service.AllService.AddressBookService.InfoByRowId(uint(iid))
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
if t.RowId > 0 {
|
||||
response.Success(c, t)
|
||||
return
|
||||
@@ -66,9 +61,9 @@ func (ct *AddressBook) Create(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
t := f.ToAddressBook()
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 {
|
||||
t.UserId = u.Id
|
||||
if t.UserId == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
@@ -98,7 +93,7 @@ func (ct *AddressBook) Create(c *gin.Context) {
|
||||
// @Param body body admin.AddressBookForm true "地址簿信息"
|
||||
// @Success 200 {object} response.Response{data=model.AddressBook}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/address_book/create [post]
|
||||
// @Router /admin/address_book/batchCreate [post]
|
||||
// @Security token
|
||||
func (ct *AddressBook) BatchCreate(c *gin.Context) {
|
||||
f := &admin.AddressBookForm{}
|
||||
@@ -111,9 +106,21 @@ func (ct *AddressBook) BatchCreate(c *gin.Context) {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
ul := len(f.UserIds)
|
||||
|
||||
if ul == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
if ul > 1 {
|
||||
//多用户置空标签
|
||||
f.Tags = []string{}
|
||||
//多用户只能创建到默认地址簿
|
||||
f.CollectionId = 0
|
||||
}
|
||||
|
||||
//创建标签
|
||||
for _, fu := range f.UserIds {
|
||||
/*for _, fu := range f.UserIds {
|
||||
if fu == 0 {
|
||||
continue
|
||||
}
|
||||
@@ -126,13 +133,13 @@ func (ct *AddressBook) BatchCreate(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
ts := f.ToAddressBooks()
|
||||
for _, t := range ts {
|
||||
if t.UserId == 0 {
|
||||
continue
|
||||
}
|
||||
ex := service.AllService.AddressBookService.InfoByUserIdAndId(t.UserId, t.Id)
|
||||
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(t.UserId, t.Id, t.CollectionId)
|
||||
if ex.RowId == 0 {
|
||||
service.AllService.AddressBookService.Create(t)
|
||||
}
|
||||
@@ -161,10 +168,6 @@ func (ct *AddressBook) List(c *gin.Context) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
|
||||
query.UserId = int(u.Id)
|
||||
}
|
||||
res := service.AllService.AddressBookService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
|
||||
tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB {
|
||||
return txc.Select("id,name")
|
||||
@@ -190,11 +193,6 @@ func (ct *AddressBook) List(c *gin.Context) {
|
||||
for _, ab := range res.AddressBooks {
|
||||
abCIds = append(abCIds, ab.CollectionId)
|
||||
}
|
||||
//获取地址簿名称
|
||||
//cRes := service.AllService.AddressBookService.ListCollection(1, 999, func(tx *gorm.DB) {
|
||||
// tx.Where("id in ?", abCIds)
|
||||
//})
|
||||
//
|
||||
response.Success(c, res)
|
||||
}
|
||||
|
||||
@@ -221,15 +219,15 @@ func (ct *AddressBook) Update(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if f.RowId == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
ex := service.AllService.AddressBookService.InfoByRowId(f.RowId)
|
||||
if ex.RowId == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
t := f.ToAddressBook()
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
@@ -270,21 +268,12 @@ func (ct *AddressBook) Delete(c *gin.Context) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
err := service.AllService.AddressBookService.Delete(t)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
if u.Id > 0 {
|
||||
err := service.AllService.AddressBookService.Delete(t)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
}
|
||||
|
||||
// ShareByWebClient
|
||||
@@ -327,3 +316,47 @@ func (ct *AddressBook) ShareByWebClient(c *gin.Context) {
|
||||
"share_token": m.ShareToken,
|
||||
})
|
||||
}
|
||||
|
||||
func (ct *AddressBook) BatchCreateFromPeers(c *gin.Context) {
|
||||
f := &admin.BatchCreateFromPeersForm{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if f.UserId == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
|
||||
if f.CollectionId != 0 {
|
||||
collection := service.AllService.AddressBookService.CollectionInfoById(f.CollectionId)
|
||||
if collection.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pl := int64(len(f.PeerIds))
|
||||
peers := service.AllService.PeerService.List(1, uint(pl), func(tx *gorm.DB) {
|
||||
tx.Where("row_id in ?", f.PeerIds)
|
||||
})
|
||||
if peers.Total == 0 || pl != peers.Total {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
|
||||
tags, _ := json.Marshal(f.Tags)
|
||||
for _, peer := range peers.Peers {
|
||||
ab := service.AllService.AddressBookService.FromPeer(peer)
|
||||
ab.Tags = tags
|
||||
ab.CollectionId = f.CollectionId
|
||||
ab.UserId = f.UserId
|
||||
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(f.UserId, ab.Id, ab.CollectionId)
|
||||
if ex.RowId != 0 {
|
||||
continue
|
||||
}
|
||||
service.AllService.AddressBookService.Create(ab)
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ import (
|
||||
type AddressBookCollection struct {
|
||||
}
|
||||
|
||||
// Detail 地址簿集合
|
||||
// @AddressBookCollections 地址簿集合
|
||||
// @Summary 地址簿集合详情
|
||||
// @Description 地址簿集合详情
|
||||
// Detail 地址簿名称
|
||||
// @Tags 地址簿名称
|
||||
// @Summary 地址簿名称详情
|
||||
// @Description 地址簿名称详情
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "ID"
|
||||
@@ -29,11 +29,6 @@ func (abc *AddressBookCollection) Detail(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
iid, _ := strconv.Atoi(id)
|
||||
t := service.AllService.AddressBookService.CollectionInfoById(uint(iid))
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
if t.Id > 0 {
|
||||
response.Success(c, t)
|
||||
return
|
||||
@@ -42,13 +37,13 @@ func (abc *AddressBookCollection) Detail(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Create 创建地址簿集合
|
||||
// @AddressBookCollections 地址簿集合
|
||||
// @Summary 创建地址簿集合
|
||||
// @Description 创建地址簿集合
|
||||
// Create 创建地址簿名称
|
||||
// @Tags 地址簿名称
|
||||
// @Summary 创建地址簿名称
|
||||
// @Description 创建地址簿名称
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body model.AddressBookCollection true "地址簿集合信息"
|
||||
// @Param body body model.AddressBookCollection true "地址簿名称信息"
|
||||
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/address_book_collection/create [post]
|
||||
@@ -64,12 +59,11 @@ func (abc *AddressBookCollection) Create(c *gin.Context) {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
//t := f.ToAddressBookCollection()
|
||||
t := f
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 {
|
||||
t.UserId = u.Id
|
||||
if f.UserId == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
t := f
|
||||
err := service.AllService.AddressBookService.CreateCollection(t)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
@@ -79,9 +73,9 @@ func (abc *AddressBookCollection) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
// List 列表
|
||||
// @AddressBookCollections 地址簿集合
|
||||
// @Summary 地址簿集合列表
|
||||
// @Description 地址簿集合列表
|
||||
// @Tags 地址簿名称
|
||||
// @Summary 地址簿名称列表
|
||||
// @Description 地址簿名称列表
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码"
|
||||
@@ -98,10 +92,6 @@ func (abc *AddressBookCollection) List(c *gin.Context) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
|
||||
query.UserId = int(u.Id)
|
||||
}
|
||||
res := service.AllService.AddressBookService.ListCollection(query.Page, query.PageSize, func(tx *gorm.DB) {
|
||||
if query.UserId > 0 {
|
||||
tx.Where("user_id = ?", query.UserId)
|
||||
@@ -111,12 +101,12 @@ func (abc *AddressBookCollection) List(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Update 编辑
|
||||
// @AddressBookCollections 地址簿集合
|
||||
// @Summary 地址簿集合编辑
|
||||
// @Description 地址簿集合编辑
|
||||
// @Tags 地址簿名称
|
||||
// @Summary 地址簿名称编辑
|
||||
// @Description 地址簿名称编辑
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body model.AddressBookCollection true "地址簿集合信息"
|
||||
// @Param body body model.AddressBookCollection true "地址簿名称信息"
|
||||
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/address_book_collection/update [post]
|
||||
@@ -137,11 +127,6 @@ func (abc *AddressBookCollection) Update(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
t := f //f.ToAddressBookCollection()
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
err := service.AllService.AddressBookService.UpdateCollection(t)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
@@ -151,12 +136,12 @@ func (abc *AddressBookCollection) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Delete 删除
|
||||
// @AddressBookCollections 地址簿集合
|
||||
// @Summary 地址簿集合删除
|
||||
// @Description 地址簿集合删除
|
||||
// @Tags 地址簿名称
|
||||
// @Summary 地址簿名称删除
|
||||
// @Description 地址簿名称删除
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body model.AddressBookCollection true "地址簿集合信息"
|
||||
// @Param body body model.AddressBookCollection true "地址簿名称信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/address_book_collection/delete [post]
|
||||
@@ -173,20 +158,15 @@ func (abc *AddressBookCollection) Delete(c *gin.Context) {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
t := service.AllService.AddressBookService.CollectionInfoById(f.Id)
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
ex := service.AllService.AddressBookService.CollectionInfoById(f.Id)
|
||||
if ex.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
if u.Id > 0 {
|
||||
err := service.AllService.AddressBookService.DeleteCollection(t)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, err.Error())
|
||||
err := service.AllService.AddressBookService.DeleteCollection(ex)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ type AddressBookCollectionRule struct {
|
||||
}
|
||||
|
||||
// List 列表
|
||||
// @AddressBookCollectionRule 地址簿集合规则
|
||||
// @Summary 地址簿集合规则列表
|
||||
// @Description 地址簿集合规则列表
|
||||
// @Tags 地址簿规则
|
||||
// @Summary 地址簿规则列表
|
||||
// @Description 地址簿规则列表
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码"
|
||||
@@ -35,10 +35,6 @@ func (abcr *AddressBookCollectionRule) List(c *gin.Context) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
|
||||
query.UserId = int(u.Id)
|
||||
}
|
||||
|
||||
res := service.AllService.AddressBookService.ListRules(query.Page, query.PageSize, func(tx *gorm.DB) {
|
||||
if query.UserId > 0 {
|
||||
@@ -51,10 +47,10 @@ func (abcr *AddressBookCollectionRule) List(c *gin.Context) {
|
||||
response.Success(c, res)
|
||||
}
|
||||
|
||||
// Detail 地址簿集合规则
|
||||
// @AddressBookCollectionRule 地址簿集合规则
|
||||
// @Summary 地址簿集合规则详情
|
||||
// @Description 地址簿集合规则详情
|
||||
// Detail 地址簿规则
|
||||
// @Tags 地址簿规则
|
||||
// @Summary 地址簿规则详情
|
||||
// @Description 地址簿规则详情
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "ID"
|
||||
@@ -66,26 +62,20 @@ func (abcr *AddressBookCollectionRule) Detail(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
iid, _ := strconv.Atoi(id)
|
||||
t := service.AllService.AddressBookService.RuleInfoById(uint(iid))
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
if t.Id > 0 {
|
||||
response.Success(c, t)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
|
||||
// Create 创建地址簿集合规则
|
||||
// @AddressBookCollectionRule 地址簿集合规则
|
||||
// @Summary 创建地址簿集合规则
|
||||
// @Description 创建地址簿集合规则
|
||||
// Create 创建地址簿规则
|
||||
// @Tags 地址簿规则
|
||||
// @Summary 创建地址簿规则
|
||||
// @Description 创建地址簿规则
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body model.AddressBookCollectionRule true "地址簿集合规则信息"
|
||||
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
|
||||
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/address_book_collection_rule/create [post]
|
||||
@@ -105,13 +95,8 @@ func (abcr *AddressBookCollectionRule) Create(c *gin.Context) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
//t := f.ToAddressBookCollection()
|
||||
t := f
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if t.UserId == 0 {
|
||||
t.UserId = u.Id
|
||||
}
|
||||
msg, res := abcr.CheckForm(u, t)
|
||||
msg, res := abcr.CheckForm(t)
|
||||
if !res {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, msg))
|
||||
return
|
||||
@@ -124,9 +109,9 @@ func (abcr *AddressBookCollectionRule) Create(c *gin.Context) {
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.AddressBookCollectionRule) (string, bool) {
|
||||
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
|
||||
return "NoAccess", false
|
||||
func (abcr *AddressBookCollectionRule) CheckForm(t *model.AddressBookCollectionRule) (string, bool) {
|
||||
if t.UserId == 0 {
|
||||
return "ParamsError", false
|
||||
}
|
||||
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
|
||||
return "ParamsError", false
|
||||
@@ -141,15 +126,7 @@ func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.Address
|
||||
if tou.Id == 0 {
|
||||
return "ItemNotFound", false
|
||||
}
|
||||
//非管理员不能分享给非本组织用户
|
||||
if tou.GroupId != u.GroupId && !service.AllService.UserService.IsAdmin(u) {
|
||||
return "NoAccess", false
|
||||
}
|
||||
} else if t.Type == model.ShareAddressBookRuleTypeGroup {
|
||||
if t.ToId != u.GroupId && !service.AllService.UserService.IsAdmin(u) {
|
||||
return "NoAccess", false
|
||||
}
|
||||
|
||||
tog := service.AllService.GroupService.InfoById(t.ToId)
|
||||
if tog.Id == 0 {
|
||||
return "ItemNotFound", false
|
||||
@@ -169,12 +146,12 @@ func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.Address
|
||||
}
|
||||
|
||||
// Update 编辑
|
||||
// @AddressBookCollectionRule 地址簿集合规则
|
||||
// @Summary 地址簿集合规则编辑
|
||||
// @Description 地址簿集合规则编辑
|
||||
// @Tags 地址簿规则
|
||||
// @Summary 地址簿规则编辑
|
||||
// @Description 地址簿规则编辑
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body model.AddressBookCollectionRule true "地址簿集合规则信息"
|
||||
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
|
||||
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/address_book_collection_rule/update [post]
|
||||
@@ -194,9 +171,8 @@ func (abcr *AddressBookCollectionRule) Update(c *gin.Context) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
t := f //f.ToAddressBookCollection()
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
msg, res := abcr.CheckForm(u, t)
|
||||
t := f
|
||||
msg, res := abcr.CheckForm(t)
|
||||
if !res {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, msg))
|
||||
return
|
||||
@@ -210,12 +186,12 @@ func (abcr *AddressBookCollectionRule) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Delete 删除
|
||||
// @AddressBookCollectionRule 地址簿集合规则
|
||||
// @Summary 地址簿集合规则删除
|
||||
// @Description 地址簿集合规则删除
|
||||
// @Tags 地址簿规则
|
||||
// @Summary 地址簿规则删除
|
||||
// @Description 地址簿规则删除
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body model.AddressBookCollectionRule true "地址簿集合规则信息"
|
||||
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/address_book_collection_rule/delete [post]
|
||||
@@ -232,20 +208,15 @@ func (abcr *AddressBookCollectionRule) Delete(c *gin.Context) {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
t := service.AllService.AddressBookService.RuleInfoById(f.Id)
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
ex := service.AllService.AddressBookService.RuleInfoById(f.Id)
|
||||
if ex.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
if t.Id > 0 {
|
||||
err := service.AllService.AddressBookService.DeleteRule(t)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, err.Error())
|
||||
err := service.AllService.AddressBookService.DeleteRule(ex)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
}
|
||||
|
||||
@@ -81,6 +81,37 @@ func (a *Audit) ConnDelete(c *gin.Context) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
}
|
||||
|
||||
// BatchConnDelete 删除
|
||||
// @Tags 链接日志
|
||||
// @Summary 链接日志批量删除
|
||||
// @Description 链接日志批量删除
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body admin.AuditConnLogIds true "链接日志"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/audit_conn/batchDelete [post]
|
||||
// @Security token
|
||||
func (a *Audit) BatchConnDelete(c *gin.Context) {
|
||||
f := &admin.AuditConnLogIds{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
if len(f.Ids) == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
|
||||
err := service.AllService.AuditService.BatchDeleteAuditConn(f.Ids)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// FileList 列表
|
||||
// @Tags 文件日志
|
||||
// @Summary 文件日志列表
|
||||
@@ -93,7 +124,7 @@ func (a *Audit) ConnDelete(c *gin.Context) {
|
||||
// @Param from_peer query int false "来源设备"
|
||||
// @Success 200 {object} response.Response{data=model.AuditFileList}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/audit_conn/list [get]
|
||||
// @Router /admin/audit_file/list [get]
|
||||
// @Security token
|
||||
func (a *Audit) FileList(c *gin.Context) {
|
||||
query := &admin.AuditQuery{}
|
||||
@@ -122,7 +153,7 @@ func (a *Audit) FileList(c *gin.Context) {
|
||||
// @Param body body model.AuditFile true "文件日志信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/audit_conn/delete [post]
|
||||
// @Router /admin/audit_file/delete [post]
|
||||
// @Security token
|
||||
func (a *Audit) FileDelete(c *gin.Context) {
|
||||
f := &model.AuditFile{}
|
||||
@@ -148,3 +179,34 @@ func (a *Audit) FileDelete(c *gin.Context) {
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
}
|
||||
|
||||
// BatchFileDelete 删除
|
||||
// @Tags 文件日志
|
||||
// @Summary 文件日志批量删除
|
||||
// @Description 文件日志批量删除
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body admin.AuditFileLogIds true "文件日志"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/audit_file/batchDelete [post]
|
||||
// @Security token
|
||||
func (a *Audit) BatchFileDelete(c *gin.Context) {
|
||||
f := &admin.AuditFileLogIds{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
if len(f.Ids) == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
|
||||
err := service.AllService.AuditService.BatchDeleteAuditFile(f.Ids)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
79
http/controller/admin/config.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"Gwen/global"
|
||||
"Gwen/http/response"
|
||||
"Gwen/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
}
|
||||
|
||||
// ServerConfig RUSTDESK服务配置
|
||||
// @Tags ADMIN
|
||||
// @Summary RUSTDESK服务配置
|
||||
// @Description 服务配置,给webclient提供api-server
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/config/server [get]
|
||||
// @Security token
|
||||
func (co *Config) ServerConfig(c *gin.Context) {
|
||||
cf := &response.ServerConfigResponse{
|
||||
IdServer: global.Config.Rustdesk.IdServer,
|
||||
Key: global.Config.Rustdesk.Key,
|
||||
RelayServer: global.Config.Rustdesk.RelayServer,
|
||||
ApiServer: global.Config.Rustdesk.ApiServer,
|
||||
}
|
||||
response.Success(c, cf)
|
||||
}
|
||||
|
||||
// AppConfig APP服务配置
|
||||
// @Tags ADMIN
|
||||
// @Summary APP服务配置
|
||||
// @Description APP服务配置
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/config/app [get]
|
||||
// @Security token
|
||||
func (co *Config) AppConfig(c *gin.Context) {
|
||||
response.Success(c, &gin.H{
|
||||
"web_client": global.Config.App.WebClient,
|
||||
})
|
||||
}
|
||||
|
||||
// AdminConfig ADMIN服务配置
|
||||
// @Tags ADMIN
|
||||
// @Summary ADMIN服务配置
|
||||
// @Description ADMIN服务配置
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/config/admin [get]
|
||||
// @Security token
|
||||
func (co *Config) AdminConfig(c *gin.Context) {
|
||||
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
hello := global.Config.Admin.Hello
|
||||
helloFile := global.Config.Admin.HelloFile
|
||||
if helloFile != "" {
|
||||
b, err := os.ReadFile(helloFile)
|
||||
if err == nil && len(b) > 0 {
|
||||
hello = string(b)
|
||||
}
|
||||
}
|
||||
|
||||
//replace {{username}} to username
|
||||
hello = strings.Replace(hello, "{{username}}", u.Username, -1)
|
||||
response.Success(c, &gin.H{
|
||||
"title": global.Config.Admin.Title,
|
||||
"hello": hello,
|
||||
})
|
||||
}
|
||||
@@ -11,12 +11,135 @@ import (
|
||||
"Gwen/service"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"github.com/mojocn/base64Captcha"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Login struct {
|
||||
}
|
||||
|
||||
// Captcha 验证码结构
|
||||
type Captcha struct {
|
||||
Id string `json:"id"` // 验证码 ID
|
||||
B64 string `json:"b64"` // base64 验证码
|
||||
Code string `json:"-"` // 验证码内容
|
||||
ExpiresAt time.Time `json:"-"` // 过期时间
|
||||
}
|
||||
type LoginLimiter struct {
|
||||
mu sync.RWMutex
|
||||
failCount map[string]int // 记录每个 IP 的失败次数
|
||||
timestamp map[string]time.Time // 记录每个 IP 的最后失败时间
|
||||
captchas map[string]Captcha // 每个 IP 的验证码
|
||||
threshold int // 失败阈值
|
||||
expiry time.Duration // 失败记录过期时间
|
||||
}
|
||||
|
||||
func NewLoginLimiter(threshold int, expiry time.Duration) *LoginLimiter {
|
||||
return &LoginLimiter{
|
||||
failCount: make(map[string]int),
|
||||
timestamp: make(map[string]time.Time),
|
||||
captchas: make(map[string]Captcha),
|
||||
threshold: threshold,
|
||||
expiry: expiry,
|
||||
}
|
||||
}
|
||||
|
||||
// RecordFailure 记录登录失败
|
||||
func (l *LoginLimiter) RecordFailure(ip string) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
// 如果该 IP 的记录已经过期,重置计数
|
||||
if lastTime, exists := l.timestamp[ip]; exists && time.Since(lastTime) > l.expiry {
|
||||
l.failCount[ip] = 0
|
||||
}
|
||||
|
||||
// 更新失败次数和时间戳
|
||||
l.failCount[ip]++
|
||||
l.timestamp[ip] = time.Now()
|
||||
}
|
||||
|
||||
// NeedsCaptcha 检查是否需要验证码
|
||||
func (l *LoginLimiter) NeedsCaptcha(ip string) bool {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
|
||||
// 检查记录是否存在且未过期
|
||||
if lastTime, exists := l.timestamp[ip]; exists && time.Since(lastTime) <= l.expiry {
|
||||
return l.failCount[ip] >= l.threshold
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GenerateCaptcha 为指定 IP 生成验证码
|
||||
func (l *LoginLimiter) GenerateCaptcha(ip string) Captcha {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
capd := base64Captcha.NewDriverString(50, 150, 5, 10, 4, "1234567890abcdefghijklmnopqrstuvwxyz", nil, nil, nil)
|
||||
b64cap := base64Captcha.NewCaptcha(capd, base64Captcha.DefaultMemStore)
|
||||
id, b64s, answer, err := b64cap.Generate()
|
||||
if err != nil {
|
||||
global.Logger.Error("Generate captcha failed: " + err.Error())
|
||||
return Captcha{}
|
||||
}
|
||||
// 保存验证码到对应 IP
|
||||
l.captchas[ip] = Captcha{
|
||||
Id: id,
|
||||
B64: b64s,
|
||||
Code: answer,
|
||||
ExpiresAt: time.Now().Add(5 * time.Minute),
|
||||
}
|
||||
return l.captchas[ip]
|
||||
}
|
||||
|
||||
// VerifyCaptcha 验证指定 IP 的验证码
|
||||
func (l *LoginLimiter) VerifyCaptcha(ip, code string) bool {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
|
||||
// 检查验证码是否存在且未过期
|
||||
if captcha, exists := l.captchas[ip]; exists && time.Now().Before(captcha.ExpiresAt) {
|
||||
return captcha.Code == code
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RemoveCaptcha 移除指定 IP 的验证码
|
||||
func (l *LoginLimiter) RemoveCaptcha(ip string) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
delete(l.captchas, ip)
|
||||
}
|
||||
|
||||
// CleanupExpired 清理过期的记录
|
||||
func (l *LoginLimiter) CleanupExpired() {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
for ip, lastTime := range l.timestamp {
|
||||
if now.Sub(lastTime) > l.expiry {
|
||||
delete(l.failCount, ip)
|
||||
delete(l.timestamp, ip)
|
||||
delete(l.captchas, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LoginLimiter) RemoveRecord(ip string) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
delete(l.failCount, ip)
|
||||
delete(l.timestamp, ip)
|
||||
delete(l.captchas, ip)
|
||||
}
|
||||
|
||||
var loginLimiter = NewLoginLimiter(3, 5*time.Minute)
|
||||
|
||||
// Login 登录
|
||||
// @Tags 登录
|
||||
// @Summary 登录
|
||||
@@ -31,40 +154,69 @@ type Login struct {
|
||||
func (ct *Login) Login(c *gin.Context) {
|
||||
f := &admin.Login{}
|
||||
err := c.ShouldBindJSON(f)
|
||||
clientIp := c.ClientIP()
|
||||
if err != nil {
|
||||
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), c.ClientIP()))
|
||||
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), clientIp))
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
errList := global.Validator.ValidStruct(c, f)
|
||||
if len(errList) > 0 {
|
||||
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), c.ClientIP()))
|
||||
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), clientIp))
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否需要验证码
|
||||
if loginLimiter.NeedsCaptcha(clientIp) {
|
||||
if f.Captcha == "" || !loginLimiter.VerifyCaptcha(clientIp, f.Captcha) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "CaptchaError"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
u := service.AllService.UserService.InfoByUsernamePassword(f.Username, f.Password)
|
||||
|
||||
if u.Id == 0 {
|
||||
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "UsernameOrPasswordError", c.RemoteIP(), c.ClientIP()))
|
||||
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "UsernameOrPasswordError", c.RemoteIP(), clientIp))
|
||||
loginLimiter.RecordFailure(clientIp)
|
||||
if loginLimiter.NeedsCaptcha(clientIp) {
|
||||
// 移除原验证码,重新生成
|
||||
loginLimiter.RemoveCaptcha(clientIp)
|
||||
response.Fail(c, 110, response.TranslateMsg(c, "UsernameOrPasswordError"))
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "UsernameOrPasswordError"))
|
||||
return
|
||||
}
|
||||
|
||||
ut := service.AllService.UserService.Login(u, &model.LoginLog{
|
||||
UserId: u.Id,
|
||||
Client: "webadmin",
|
||||
Client: model.LoginLogClientWebAdmin,
|
||||
Uuid: "", //must be empty
|
||||
Ip: c.ClientIP(),
|
||||
Type: "account",
|
||||
Ip: clientIp,
|
||||
Type: model.LoginLogTypeAccount,
|
||||
Platform: f.Platform,
|
||||
})
|
||||
|
||||
response.Success(c, &adResp.LoginPayload{
|
||||
Token: ut.Token,
|
||||
Username: u.Username,
|
||||
RouteNames: service.AllService.UserService.RouteNames(u),
|
||||
Nickname: u.Nickname,
|
||||
// 成功后清除记录
|
||||
loginLimiter.RemoveRecord(clientIp)
|
||||
|
||||
// 清理过期记录
|
||||
go loginLimiter.CleanupExpired()
|
||||
|
||||
responseLoginSuccess(c, u, ut.Token)
|
||||
}
|
||||
func (ct *Login) Captcha(c *gin.Context) {
|
||||
clientIp := c.ClientIP()
|
||||
if !loginLimiter.NeedsCaptcha(clientIp) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoCaptchaRequired"))
|
||||
return
|
||||
}
|
||||
captcha := loginLimiter.GenerateCaptcha(clientIp)
|
||||
response.Success(c, gin.H{
|
||||
"captcha": captcha,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -96,14 +248,13 @@ func (ct *Login) Logout(c *gin.Context) {
|
||||
// @Failure 500 {object} response.ErrorResponse
|
||||
// @Router /admin/login-options [post]
|
||||
func (ct *Login) LoginOptions(c *gin.Context) {
|
||||
res := service.AllService.OauthService.List(1, 100, func(tx *gorm.DB) {
|
||||
tx.Select("op").Order("id")
|
||||
ip := c.ClientIP()
|
||||
ops := service.AllService.OauthService.GetOauthProviders()
|
||||
response.Success(c, gin.H{
|
||||
"ops": ops,
|
||||
"register": global.Config.App.Register,
|
||||
"need_captcha": loginLimiter.NeedsCaptcha(ip),
|
||||
})
|
||||
var ops []string
|
||||
for _, v := range res.Oauths {
|
||||
ops = append(ops, v.Op)
|
||||
}
|
||||
response.Success(c, ops)
|
||||
}
|
||||
|
||||
// OidcAuth
|
||||
@@ -160,12 +311,13 @@ func (ct *Login) OidcAuthQuery(c *gin.Context) {
|
||||
if ut == nil {
|
||||
return
|
||||
}
|
||||
//fmt.Println("u:", u)
|
||||
//fmt.Println("ut:", ut)
|
||||
response.Success(c, &adResp.LoginPayload{
|
||||
Token: ut.Token,
|
||||
Username: u.Username,
|
||||
RouteNames: service.AllService.UserService.RouteNames(u),
|
||||
Nickname: u.Nickname,
|
||||
})
|
||||
responseLoginSuccess(c, u, ut.Token)
|
||||
}
|
||||
|
||||
func responseLoginSuccess(c *gin.Context, u *model.User, token string) {
|
||||
lp := &adResp.LoginPayload{}
|
||||
lp.FromUser(u)
|
||||
lp.Token = token
|
||||
lp.RouteNames = service.AllService.UserService.RouteNames(u)
|
||||
response.Success(c, lp)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ type LoginLog struct {
|
||||
// @Param id path int true "ID"
|
||||
// @Success 200 {object} response.Response{data=model.LoginLog}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/loginLog/detail/{id} [get]
|
||||
// @Router /admin/login_log/detail/{id} [get]
|
||||
// @Security token
|
||||
func (ct *LoginLog) Detail(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
@@ -48,7 +48,7 @@ func (ct *LoginLog) Detail(c *gin.Context) {
|
||||
// @Param user_id query int false "用户ID"
|
||||
// @Success 200 {object} response.Response{data=model.LoginLogList}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/loginLog/list [get]
|
||||
// @Router /admin/login_log/list [get]
|
||||
// @Security token
|
||||
func (ct *LoginLog) List(c *gin.Context) {
|
||||
query := &admin.LoginLogQuery{}
|
||||
@@ -56,10 +56,6 @@ func (ct *LoginLog) List(c *gin.Context) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
|
||||
query.UserId = int(u.Id)
|
||||
}
|
||||
res := service.AllService.LoginLogService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
|
||||
if query.UserId > 0 {
|
||||
tx.Where("user_id = ?", query.UserId)
|
||||
@@ -78,7 +74,7 @@ func (ct *LoginLog) List(c *gin.Context) {
|
||||
// @Param body body model.LoginLog true "登录日志信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/loginLog/delete [post]
|
||||
// @Router /admin/login_log/delete [post]
|
||||
// @Security token
|
||||
func (ct *LoginLog) Delete(c *gin.Context) {
|
||||
f := &model.LoginLog{}
|
||||
@@ -93,19 +89,45 @@ func (ct *LoginLog) Delete(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
l := service.AllService.LoginLogService.InfoById(f.Id)
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) && l.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
if l.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
if l.Id > 0 {
|
||||
err := service.AllService.LoginLogService.Delete(l)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, err.Error())
|
||||
err := service.AllService.LoginLogService.Delete(l)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
response.Fail(c, 101, err.Error())
|
||||
}
|
||||
|
||||
// BatchDelete 删除
|
||||
// @Tags 登录日志
|
||||
// @Summary 登录日志批量删除
|
||||
// @Description 登录日志批量删除
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body admin.LoginLogIds true "登录日志"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/login_log/batchDelete [post]
|
||||
// @Security token
|
||||
func (ct *LoginLog) BatchDelete(c *gin.Context) {
|
||||
f := &admin.LoginLogIds{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
if len(f.Ids) == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
|
||||
err := service.AllService.LoginLogService.BatchDelete(f.Ids)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
271
http/controller/admin/my/addressBook.go
Normal file
@@ -0,0 +1,271 @@
|
||||
package my
|
||||
|
||||
import (
|
||||
"Gwen/global"
|
||||
"Gwen/http/request/admin"
|
||||
"Gwen/http/response"
|
||||
"Gwen/service"
|
||||
"encoding/json"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AddressBook struct{}
|
||||
|
||||
// List 列表
|
||||
// @Tags 我的地址簿
|
||||
// @Summary 地址簿列表
|
||||
// @Description 地址簿列表
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "页大小"
|
||||
// @Param user_id query int false "用户id"
|
||||
// @Success 200 {object} response.Response{data=model.AddressBookList}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/address_book/list [get]
|
||||
// @Security token
|
||||
func (ct *AddressBook) List(c *gin.Context) {
|
||||
query := &admin.AddressBookQuery{}
|
||||
if err := c.ShouldBindQuery(query); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
query.UserId = int(u.Id)
|
||||
res := service.AllService.AddressBookService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
|
||||
//预加载地址簿名称
|
||||
tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB {
|
||||
return txc.Select("id,name")
|
||||
})
|
||||
if query.Id != "" {
|
||||
tx.Where("id like ?", "%"+query.Id+"%")
|
||||
}
|
||||
tx.Where("user_id = ?", query.UserId)
|
||||
if query.Username != "" {
|
||||
tx.Where("username like ?", "%"+query.Username+"%")
|
||||
}
|
||||
if query.Hostname != "" {
|
||||
tx.Where("hostname like ?", "%"+query.Hostname+"%")
|
||||
}
|
||||
if query.CollectionId != nil && *query.CollectionId >= 0 {
|
||||
tx.Where("collection_id = ?", query.CollectionId)
|
||||
}
|
||||
})
|
||||
|
||||
abCIds := make([]uint, 0)
|
||||
for _, ab := range res.AddressBooks {
|
||||
abCIds = append(abCIds, ab.CollectionId)
|
||||
}
|
||||
response.Success(c, res)
|
||||
}
|
||||
|
||||
// Create 创建地址簿
|
||||
// @Tags 我的地址簿
|
||||
// @Summary 创建地址簿
|
||||
// @Description 创建地址簿
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body admin.AddressBookForm true "地址簿信息"
|
||||
// @Success 200 {object} response.Response{data=model.AddressBook}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/address_book/create [post]
|
||||
// @Security token
|
||||
func (ct *AddressBook) Create(c *gin.Context) {
|
||||
f := &admin.AddressBookForm{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
errList := global.Validator.ValidStruct(c, f)
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
t := f.ToAddressBook()
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
t.UserId = u.Id
|
||||
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
|
||||
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(t.UserId, t.Id, t.CollectionId)
|
||||
if ex.RowId > 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemExists"))
|
||||
return
|
||||
}
|
||||
|
||||
err := service.AllService.AddressBookService.Create(t)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// Update 编辑
|
||||
// @Tags 我的地址簿
|
||||
// @Summary 地址簿编辑
|
||||
// @Description 地址簿编辑
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body admin.AddressBookForm true "地址簿信息"
|
||||
// @Success 200 {object} response.Response{data=model.AddressBook}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/address_book/update [post]
|
||||
// @Security token
|
||||
func (ct *AddressBook) Update(c *gin.Context) {
|
||||
f := &admin.AddressBookForm{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
errList := global.Validator.ValidStruct(c, f)
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
if f.RowId == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if f.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
|
||||
ex := service.AllService.AddressBookService.InfoByRowId(f.RowId)
|
||||
if ex.RowId == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
if ex.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
t := f.ToAddressBook()
|
||||
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
err := service.AllService.AddressBookService.UpdateAll(t)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// Delete 删除
|
||||
// @Tags 我的地址簿
|
||||
// @Summary 地址簿删除
|
||||
// @Description 地址簿删除
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body admin.AddressBookForm true "地址簿信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/address_book/delete [post]
|
||||
// @Security token
|
||||
func (ct *AddressBook) Delete(c *gin.Context) {
|
||||
f := &admin.AddressBookForm{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
id := f.RowId
|
||||
errList := global.Validator.ValidVar(c, id, "required,gt=0")
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
ex := service.AllService.AddressBookService.InfoByRowId(f.RowId)
|
||||
if ex.RowId == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if ex.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
err := service.AllService.AddressBookService.Delete(ex)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
func (ct *AddressBook) BatchCreateFromPeers(c *gin.Context) {
|
||||
f := &admin.BatchCreateFromPeersForm{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
|
||||
if f.CollectionId != 0 {
|
||||
collection := service.AllService.AddressBookService.CollectionInfoById(f.CollectionId)
|
||||
if collection.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
if collection.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(f.PeerIds) == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
pl := int64(len(f.PeerIds))
|
||||
peers := service.AllService.PeerService.List(1, uint(pl), func(tx *gorm.DB) {
|
||||
tx.Where("row_id in ?", f.PeerIds)
|
||||
tx.Where("user_id = ?", u.Id)
|
||||
})
|
||||
if peers.Total == 0 || pl != peers.Total {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
|
||||
tags, _ := json.Marshal(f.Tags)
|
||||
for _, peer := range peers.Peers {
|
||||
ab := service.AllService.AddressBookService.FromPeer(peer)
|
||||
ab.Tags = tags
|
||||
ab.CollectionId = f.CollectionId
|
||||
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(u.Id, ab.Id, ab.CollectionId)
|
||||
if ex.RowId != 0 {
|
||||
continue
|
||||
}
|
||||
service.AllService.AddressBookService.Create(ab)
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func (ct *AddressBook) BatchUpdateTags(c *gin.Context) {
|
||||
f := &admin.BatchUpdateTagsForm{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
|
||||
abs := service.AllService.AddressBookService.List(1, 999, func(tx *gorm.DB) {
|
||||
tx.Where("row_id in ?", f.RowIds)
|
||||
tx.Where("user_id = ?", u.Id)
|
||||
})
|
||||
if abs.Total == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
err := service.AllService.AddressBookService.BatchUpdateTags(abs.AddressBooks, f.Tags)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
162
http/controller/admin/my/addressBookCollection.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package my
|
||||
|
||||
import (
|
||||
"Gwen/global"
|
||||
"Gwen/http/request/admin"
|
||||
"Gwen/http/response"
|
||||
"Gwen/model"
|
||||
"Gwen/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AddressBookCollection struct {
|
||||
}
|
||||
|
||||
// Create 创建地址簿名称
|
||||
// @Tags 我的地址簿名称
|
||||
// @Summary 创建地址簿名称
|
||||
// @Description 创建地址簿名称
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body model.AddressBookCollection true "地址簿名称信息"
|
||||
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/address_book_collection/create [post]
|
||||
// @Security token
|
||||
func (abc *AddressBookCollection) Create(c *gin.Context) {
|
||||
f := &model.AddressBookCollection{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
errList := global.Validator.ValidStruct(c, f)
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
f.UserId = u.Id
|
||||
err := service.AllService.AddressBookService.CreateCollection(f)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// List 列表
|
||||
// @Tags 我的地址簿名称
|
||||
// @Summary 地址簿名称列表
|
||||
// @Description 地址簿名称列表
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "页大小"
|
||||
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/address_book_collection/list [get]
|
||||
// @Security token
|
||||
func (abc *AddressBookCollection) List(c *gin.Context) {
|
||||
query := &admin.AddressBookCollectionQuery{}
|
||||
if err := c.ShouldBindQuery(query); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
query.UserId = int(u.Id)
|
||||
res := service.AllService.AddressBookService.ListCollection(query.Page, query.PageSize, func(tx *gorm.DB) {
|
||||
tx.Where("user_id = ?", query.UserId)
|
||||
})
|
||||
response.Success(c, res)
|
||||
}
|
||||
|
||||
// Update 编辑
|
||||
// @Tags 我的地址簿名称
|
||||
// @Summary 地址簿名称编辑
|
||||
// @Description 地址簿名称编辑
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body model.AddressBookCollection true "地址簿名称信息"
|
||||
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/address_book_collection/update [post]
|
||||
// @Security token
|
||||
func (abc *AddressBookCollection) Update(c *gin.Context) {
|
||||
f := &model.AddressBookCollection{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
errList := global.Validator.ValidStruct(c, f)
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
if f.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if f.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
ex := service.AllService.AddressBookService.CollectionInfoById(f.Id)
|
||||
if ex.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
if ex.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
|
||||
err := service.AllService.AddressBookService.UpdateCollection(f)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// Delete 删除
|
||||
// @Tags 我的地址簿名称
|
||||
// @Summary 地址簿名称删除
|
||||
// @Description 地址簿名称删除
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body model.AddressBookCollection true "地址簿名称信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/address_book_collection/delete [post]
|
||||
// @Security token
|
||||
func (abc *AddressBookCollection) Delete(c *gin.Context) {
|
||||
f := &model.AddressBookCollection{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
id := f.Id
|
||||
errList := global.Validator.ValidVar(c, id, "required,gt=0")
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
ex := service.AllService.AddressBookService.CollectionInfoById(f.Id)
|
||||
if ex.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if ex.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
err := service.AllService.AddressBookService.DeleteCollection(ex)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
}
|
||||
228
http/controller/admin/my/addressBookCollectionRule.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package my
|
||||
|
||||
import (
|
||||
"Gwen/global"
|
||||
"Gwen/http/request/admin"
|
||||
"Gwen/http/response"
|
||||
"Gwen/model"
|
||||
"Gwen/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AddressBookCollectionRule struct {
|
||||
}
|
||||
|
||||
// List 列表
|
||||
// @Tags 我的地址簿规则
|
||||
// @Summary 地址簿规则列表
|
||||
// @Description 地址簿规则列表
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "页大小"
|
||||
// @Param is_my query int false "是否是我的"
|
||||
// @Param user_id query int false "用户id"
|
||||
// @Param collection_id query int false "地址簿集合id"
|
||||
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/address_book_collection_rule/list [get]
|
||||
// @Security token
|
||||
func (abcr *AddressBookCollectionRule) List(c *gin.Context) {
|
||||
query := &admin.AddressBookCollectionRuleQuery{}
|
||||
if err := c.ShouldBindQuery(query); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
query.UserId = int(u.Id)
|
||||
|
||||
res := service.AllService.AddressBookService.ListRules(query.Page, query.PageSize, func(tx *gorm.DB) {
|
||||
tx.Where("user_id = ?", query.UserId)
|
||||
if query.CollectionId > 0 {
|
||||
tx.Where("collection_id = ?", query.CollectionId)
|
||||
}
|
||||
})
|
||||
response.Success(c, res)
|
||||
}
|
||||
|
||||
// Create 创建地址簿规则
|
||||
// @Tags 我的地址簿规则
|
||||
// @Summary 创建地址簿规则
|
||||
// @Description 创建地址簿规则
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
|
||||
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/address_book_collection_rule/create [post]
|
||||
// @Security token
|
||||
func (abcr *AddressBookCollectionRule) Create(c *gin.Context) {
|
||||
f := &model.AddressBookCollectionRule{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
errList := global.Validator.ValidStruct(c, f)
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
if f.Type != model.ShareAddressBookRuleTypePersonal && f.Type != model.ShareAddressBookRuleTypeGroup {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
//t := f.ToAddressBookCollection()
|
||||
t := f
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
t.UserId = u.Id
|
||||
msg, res := abcr.CheckForm(u, t)
|
||||
if !res {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, msg))
|
||||
return
|
||||
}
|
||||
err := service.AllService.AddressBookService.CreateRule(t)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.AddressBookCollectionRule) (string, bool) {
|
||||
if t.UserId != u.Id {
|
||||
return "NoAccess", false
|
||||
}
|
||||
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
|
||||
return "ParamsError", false
|
||||
}
|
||||
|
||||
//check to_id
|
||||
if t.Type == model.ShareAddressBookRuleTypePersonal {
|
||||
if t.ToId == t.UserId {
|
||||
return "ParamsError", false
|
||||
}
|
||||
tou := service.AllService.UserService.InfoById(t.ToId)
|
||||
if tou.Id == 0 {
|
||||
return "ItemNotFound", false
|
||||
}
|
||||
//非管理员不能分享给非本组织用户
|
||||
if tou.GroupId != u.GroupId {
|
||||
return "NoAccess", false
|
||||
}
|
||||
} else if t.Type == model.ShareAddressBookRuleTypeGroup {
|
||||
//非管理员不能分享给其他组
|
||||
if t.ToId != u.GroupId {
|
||||
return "NoAccess", false
|
||||
}
|
||||
|
||||
tog := service.AllService.GroupService.InfoById(t.ToId)
|
||||
if tog.Id == 0 {
|
||||
return "ItemNotFound", false
|
||||
}
|
||||
} else {
|
||||
return "ParamsError", false
|
||||
}
|
||||
// 重复检查
|
||||
ex := service.AllService.AddressBookService.RulePersonalInfoByToIdAndCid(t.ToId, t.CollectionId)
|
||||
if t.Id == 0 && ex.Id > 0 {
|
||||
return "ItemExists", false
|
||||
}
|
||||
if t.Id > 0 && ex.Id > 0 && t.Id != ex.Id {
|
||||
return "ItemExists", false
|
||||
}
|
||||
return "", true
|
||||
}
|
||||
|
||||
// Update 编辑
|
||||
// @Tags 我的地址簿规则
|
||||
// @Summary 地址簿规则编辑
|
||||
// @Description 地址簿规则编辑
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
|
||||
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/address_book_collection_rule/update [post]
|
||||
// @Security token
|
||||
func (abcr *AddressBookCollectionRule) Update(c *gin.Context) {
|
||||
f := &model.AddressBookCollectionRule{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
errList := global.Validator.ValidStruct(c, f)
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
if f.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
|
||||
ex := service.AllService.AddressBookService.RuleInfoById(f.Id)
|
||||
if ex.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
if ex.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
t := f
|
||||
msg, res := abcr.CheckForm(u, t)
|
||||
if !res {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, msg))
|
||||
return
|
||||
}
|
||||
err := service.AllService.AddressBookService.UpdateRule(t)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// Delete 删除
|
||||
// @Tags 我的地址簿规则
|
||||
// @Summary 地址簿规则删除
|
||||
// @Description 地址簿规则删除
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/address_book_collection_rule/delete [post]
|
||||
// @Security token
|
||||
func (abcr *AddressBookCollectionRule) Delete(c *gin.Context) {
|
||||
f := &model.AddressBookCollectionRule{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
id := f.Id
|
||||
errList := global.Validator.ValidVar(c, id, "required,gt=0")
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
ex := service.AllService.AddressBookService.RuleInfoById(f.Id)
|
||||
if ex.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if ex.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
|
||||
err := service.AllService.AddressBookService.DeleteRule(ex)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
}
|
||||
113
http/controller/admin/my/loginLog.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package my
|
||||
|
||||
import (
|
||||
"Gwen/global"
|
||||
"Gwen/http/request/admin"
|
||||
"Gwen/http/response"
|
||||
"Gwen/model"
|
||||
"Gwen/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LoginLog struct {
|
||||
}
|
||||
|
||||
// List 列表
|
||||
// @Tags 我的登录日志
|
||||
// @Summary 登录日志列表
|
||||
// @Description 登录日志列表
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "页大小"
|
||||
// @Param user_id query int false "用户ID"
|
||||
// @Success 200 {object} response.Response{data=model.LoginLogList}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/login_log/list [get]
|
||||
// @Security token
|
||||
func (ct *LoginLog) List(c *gin.Context) {
|
||||
query := &admin.LoginLogQuery{}
|
||||
if err := c.ShouldBindQuery(query); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
res := service.AllService.LoginLogService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
|
||||
tx.Where("user_id = ? and is_deleted = ?", u.Id, model.IsDeletedNo)
|
||||
tx.Order("id desc")
|
||||
})
|
||||
response.Success(c, res)
|
||||
}
|
||||
|
||||
// Delete 删除
|
||||
// @Tags 我的登录日志
|
||||
// @Summary 登录日志删除
|
||||
// @Description 登录日志删除
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body model.LoginLog true "登录日志信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/login_log/delete [post]
|
||||
// @Security token
|
||||
func (ct *LoginLog) Delete(c *gin.Context) {
|
||||
f := &model.LoginLog{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
id := f.Id
|
||||
errList := global.Validator.ValidVar(c, id, "required,gt=0")
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
l := service.AllService.LoginLogService.InfoById(f.Id)
|
||||
if l.Id == 0 || l.IsDeleted == model.IsDeletedYes {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if l.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
err := service.AllService.LoginLogService.SoftDelete(l)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, err.Error())
|
||||
}
|
||||
|
||||
// BatchDelete 删除
|
||||
// @Tags 我的登录日志
|
||||
// @Summary 登录日志批量删除
|
||||
// @Description 登录日志批量删除
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body admin.LoginLogIds true "登录日志"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/login_log/batchDelete [post]
|
||||
// @Security token
|
||||
func (ct *LoginLog) BatchDelete(c *gin.Context) {
|
||||
f := &admin.LoginLogIds{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
if len(f.Ids) == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
err := service.AllService.LoginLogService.BatchSoftDelete(u.Id, f.Ids)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, err.Error())
|
||||
return
|
||||
}
|
||||
59
http/controller/admin/my/peer.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package my
|
||||
|
||||
import (
|
||||
"Gwen/http/request/admin"
|
||||
"Gwen/http/response"
|
||||
"Gwen/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Peer struct {
|
||||
}
|
||||
|
||||
// List 列表
|
||||
// @Tags 我的设备
|
||||
// @Summary 设备列表
|
||||
// @Description 设备列表
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "页大小"
|
||||
// @Param time_ago query int false "时间"
|
||||
// @Param id query string false "ID"
|
||||
// @Param hostname query string false "主机名"
|
||||
// @Param uuids query string false "uuids 用逗号分隔"
|
||||
// @Success 200 {object} response.Response{data=model.PeerList}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/peer/list [get]
|
||||
// @Security token
|
||||
func (ct *Peer) List(c *gin.Context) {
|
||||
query := &admin.PeerQuery{}
|
||||
if err := c.ShouldBindQuery(query); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
res := service.AllService.PeerService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
|
||||
tx.Where("user_id = ?", u.Id)
|
||||
if query.TimeAgo > 0 {
|
||||
lt := time.Now().Unix() - int64(query.TimeAgo)
|
||||
tx.Where("last_online_time < ?", lt)
|
||||
}
|
||||
if query.TimeAgo < 0 {
|
||||
lt := time.Now().Unix() + int64(query.TimeAgo)
|
||||
tx.Where("last_online_time > ?", lt)
|
||||
}
|
||||
if query.Id != "" {
|
||||
tx.Where("id like ?", "%"+query.Id+"%")
|
||||
}
|
||||
if query.Hostname != "" {
|
||||
tx.Where("hostname like ?", "%"+query.Hostname+"%")
|
||||
}
|
||||
if query.Uuids != "" {
|
||||
tx.Where("uuid in (?)", query.Uuids)
|
||||
}
|
||||
})
|
||||
response.Success(c, res)
|
||||
}
|
||||
119
http/controller/admin/my/shareRecord.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package my
|
||||
|
||||
import (
|
||||
"Gwen/global"
|
||||
"Gwen/http/request/admin"
|
||||
"Gwen/http/response"
|
||||
"Gwen/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ShareRecord struct {
|
||||
}
|
||||
|
||||
// List 分享记录列表
|
||||
// @Tags 我的分享记录
|
||||
// @Summary 分享记录列表
|
||||
// @Description 分享记录列表
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "页大小"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/share_record/list [get]
|
||||
// @Security token
|
||||
func (sr *ShareRecord) List(c *gin.Context) {
|
||||
query := &admin.PageQuery{}
|
||||
if err := c.ShouldBindQuery(query); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
res := service.AllService.ShareRecordService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
|
||||
tx.Where("user_id = ?", u.Id)
|
||||
})
|
||||
response.Success(c, res)
|
||||
}
|
||||
|
||||
// Delete 分享记录删除
|
||||
// @Tags 我的分享记录
|
||||
// @Summary 分享记录删除
|
||||
// @Description 分享记录删除
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body admin.ShareRecordForm true "分享记录信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/share_record/delete [post]
|
||||
// @Security token
|
||||
func (sr *ShareRecord) Delete(c *gin.Context) {
|
||||
f := &admin.ShareRecordForm{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
id := f.Id
|
||||
errList := global.Validator.ValidVar(c, id, "required,gt=0")
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
i := service.AllService.ShareRecordService.InfoById(f.Id)
|
||||
if i.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
if i.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
err := service.AllService.ShareRecordService.Delete(i)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
}
|
||||
|
||||
// BatchDelete 批量删除我的分享记录
|
||||
// @Tags 我的
|
||||
// @Summary 批量删除我的分享记录
|
||||
// @Description 批量删除我的分享记录
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body admin.PeerShareRecordBatchDeleteForm true "id"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/share_record/batchDelete [post]
|
||||
// @Security token
|
||||
func (sr *ShareRecord) BatchDelete(c *gin.Context) {
|
||||
f := &admin.PeerShareRecordBatchDeleteForm{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
if len(f.Ids) == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
var l int64
|
||||
l = int64(len(f.Ids))
|
||||
res := service.AllService.ShareRecordService.List(1, uint(l), func(tx *gorm.DB) {
|
||||
tx.Where("user_id = ?", u.Id)
|
||||
tx.Where("id in ?", f.Ids)
|
||||
})
|
||||
if res.Total != l {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
err := service.AllService.ShareRecordService.BatchDelete(f.Ids)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
176
http/controller/admin/my/tag.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package my
|
||||
|
||||
import (
|
||||
"Gwen/global"
|
||||
"Gwen/http/request/admin"
|
||||
"Gwen/http/response"
|
||||
"Gwen/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Tag struct{}
|
||||
|
||||
// List 列表
|
||||
// @Tags 我的标签
|
||||
// @Summary 标签列表
|
||||
// @Description 标签列表
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "页大小"
|
||||
// @Param is_my query int false "是否是我的"
|
||||
// @Param user_id query int false "用户id"
|
||||
// @Success 200 {object} response.Response{data=model.TagList}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/tag/list [get]
|
||||
// @Security token
|
||||
func (ct *Tag) List(c *gin.Context) {
|
||||
query := &admin.TagQuery{}
|
||||
if err := c.ShouldBindQuery(query); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
query.UserId = int(u.Id)
|
||||
res := service.AllService.TagService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
|
||||
tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB {
|
||||
return txc.Select("id,name")
|
||||
})
|
||||
tx.Where("user_id = ?", query.UserId)
|
||||
if query.CollectionId != nil && *query.CollectionId >= 0 {
|
||||
tx.Where("collection_id = ?", query.CollectionId)
|
||||
}
|
||||
})
|
||||
response.Success(c, res)
|
||||
}
|
||||
|
||||
// Create 创建标签
|
||||
// @Tags 我的标签
|
||||
// @Summary 创建标签
|
||||
// @Description 创建标签
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body admin.TagForm true "标签信息"
|
||||
// @Success 200 {object} response.Response{data=model.Tag}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/tag/create [post]
|
||||
// @Security token
|
||||
func (ct *Tag) Create(c *gin.Context) {
|
||||
f := &admin.TagForm{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
errList := global.Validator.ValidStruct(c, f)
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
t := f.ToTag()
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
t.UserId = u.Id
|
||||
err := service.AllService.TagService.Create(t)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// Update 编辑
|
||||
// @Tags 我的标签
|
||||
// @Summary 标签编辑
|
||||
// @Description 标签编辑
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body admin.TagForm true "标签信息"
|
||||
// @Success 200 {object} response.Response{data=model.Tag}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/tag/update [post]
|
||||
// @Security token
|
||||
func (ct *Tag) Update(c *gin.Context) {
|
||||
f := &admin.TagForm{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
errList := global.Validator.ValidStruct(c, f)
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
if f.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if f.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
ex := service.AllService.TagService.InfoById(f.Id)
|
||||
if ex.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
if ex.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
|
||||
t := f.ToTag()
|
||||
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
err := service.AllService.TagService.Update(t)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// Delete 删除
|
||||
// @Tags 标签
|
||||
// @Summary 标签删除
|
||||
// @Description 标签删除
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body admin.TagForm true "标签信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/my/tag/delete [post]
|
||||
// @Security token
|
||||
func (ct *Tag) Delete(c *gin.Context) {
|
||||
f := &admin.TagForm{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
id := f.Id
|
||||
errList := global.Validator.ValidVar(c, id, "required,gt=0")
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
ex := service.AllService.TagService.InfoById(f.Id)
|
||||
if ex.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if ex.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
err := service.AllService.TagService.Delete(ex)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"Gwen/http/request/admin"
|
||||
adminReq "Gwen/http/request/admin"
|
||||
"Gwen/http/response"
|
||||
"Gwen/model"
|
||||
"Gwen/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
@@ -96,21 +95,23 @@ func (o *Oauth) BindConfirm(c *gin.Context) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
v := service.AllService.OauthService.GetOauthCache(j.Code)
|
||||
if v == nil {
|
||||
oauthService := service.AllService.OauthService
|
||||
oauthCache := oauthService.GetOauthCache(j.Code)
|
||||
if oauthCache == nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OauthExpired"))
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
err = service.AllService.OauthService.BindOauthUser(v.Op, v.ThirdOpenId, v.ThirdName, u.Id)
|
||||
oauthUser := oauthCache.ToOauthUser()
|
||||
user := service.AllService.UserService.CurUser(c)
|
||||
err = oauthService.BindOauthUser(user.Id, oauthUser, oauthCache.Op)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "BindFail"))
|
||||
return
|
||||
}
|
||||
|
||||
v.UserId = u.Id
|
||||
service.AllService.OauthService.SetOauthCache(j.Code, v, 0)
|
||||
response.Success(c, v)
|
||||
oauthCache.UserId = user.Id
|
||||
oauthService.SetOauthCache(j.Code, oauthCache, 0)
|
||||
response.Success(c, oauthCache)
|
||||
}
|
||||
|
||||
func (o *Oauth) Unbind(c *gin.Context) {
|
||||
@@ -126,28 +127,11 @@ func (o *Oauth) Unbind(c *gin.Context) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
if f.Op == model.OauthTypeGithub {
|
||||
err = service.AllService.OauthService.UnBindGithubUser(u.Id)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
err = service.AllService.OauthService.UnBindOauthUser(u.Id, f.Op)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
if f.Op == model.OauthTypeGoogle {
|
||||
err = service.AllService.OauthService.UnBindGoogleUser(u.Id)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
if f.Op == model.OauthTypeOidc {
|
||||
err = service.AllService.OauthService.UnBindOidcUser(u.Id)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
@@ -196,15 +180,18 @@ func (o *Oauth) Create(c *gin.Context) {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
|
||||
ex := service.AllService.OauthService.InfoByOp(f.Op)
|
||||
u := f.ToOauth()
|
||||
err := u.FormatOauthInfo()
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
ex := service.AllService.OauthService.InfoByOp(u.Op)
|
||||
if ex.Id > 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemExists"))
|
||||
return
|
||||
}
|
||||
|
||||
u := f.ToOauth()
|
||||
err := service.AllService.OauthService.Create(u)
|
||||
err = service.AllService.OauthService.Create(u)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
|
||||
@@ -79,6 +79,7 @@ func (ct *Peer) Create(c *gin.Context) {
|
||||
// @Param time_ago query int false "时间"
|
||||
// @Param id query string false "ID"
|
||||
// @Param hostname query string false "主机名"
|
||||
// @Param uuids query string false "uuids 用逗号分隔"
|
||||
// @Success 200 {object} response.Response{data=model.PeerList}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/peer/list [get]
|
||||
@@ -104,6 +105,9 @@ func (ct *Peer) List(c *gin.Context) {
|
||||
if query.Hostname != "" {
|
||||
tx.Where("hostname like ?", "%"+query.Hostname+"%")
|
||||
}
|
||||
if query.Uuids != "" {
|
||||
tx.Where("uuid in (?)", query.Uuids)
|
||||
}
|
||||
})
|
||||
response.Success(c, res)
|
||||
}
|
||||
@@ -188,7 +192,7 @@ func (ct *Peer) Delete(c *gin.Context) {
|
||||
// @Param body body admin.PeerBatchDeleteForm true "设备id"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/peer/delete [post]
|
||||
// @Router /admin/peer/batchDelete [post]
|
||||
// @Security token
|
||||
func (ct *Peer) BatchDelete(c *gin.Context) {
|
||||
f := &admin.PeerBatchDeleteForm{}
|
||||
|
||||
@@ -2,45 +2,127 @@ package admin
|
||||
|
||||
import (
|
||||
"Gwen/global"
|
||||
"Gwen/http/request/admin"
|
||||
"Gwen/http/response"
|
||||
"Gwen/model"
|
||||
"Gwen/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Rustdesk struct {
|
||||
}
|
||||
|
||||
// ServerConfig RUSTDESK服务配置
|
||||
// @Tags ADMIN
|
||||
// @Summary RUSTDESK服务配置
|
||||
// @Description 服务配置,给webclient提供api-server
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/server-config [get]
|
||||
// @Security token
|
||||
func (r *Rustdesk) ServerConfig(c *gin.Context) {
|
||||
cf := &response.ServerConfigResponse{
|
||||
IdServer: global.Config.Rustdesk.IdServer,
|
||||
Key: global.Config.Rustdesk.Key,
|
||||
RelayServer: global.Config.Rustdesk.RelayServer,
|
||||
ApiServer: global.Config.Rustdesk.ApiServer,
|
||||
}
|
||||
response.Success(c, cf)
|
||||
type RustdeskCmd struct {
|
||||
Cmd string `json:"cmd"`
|
||||
Option string `json:"option"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
// AppConfig APP服务配置
|
||||
// @Tags ADMIN
|
||||
// @Summary APP服务配置
|
||||
// @Description APP服务配置
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/app-config [get]
|
||||
// @Security token
|
||||
func (r *Rustdesk) AppConfig(c *gin.Context) {
|
||||
response.Success(c, &gin.H{
|
||||
"web_client": global.Config.App.WebClient,
|
||||
})
|
||||
func (r *Rustdesk) CmdList(c *gin.Context) {
|
||||
q := &admin.PageQuery{}
|
||||
if err := c.ShouldBindQuery(q); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
res := service.AllService.ServerCmdService.List(q.Page, 9999)
|
||||
//在列表前添加系统命令
|
||||
list := make([]*model.ServerCmd, 0)
|
||||
list = append(list, model.SysIdServerCmds...)
|
||||
list = append(list, model.SysRelayServerCmds...)
|
||||
list = append(list, res.ServerCmds...)
|
||||
res.ServerCmds = list
|
||||
response.Success(c, res)
|
||||
}
|
||||
|
||||
func (r *Rustdesk) CmdDelete(c *gin.Context) {
|
||||
f := &model.ServerCmd{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
if f.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
|
||||
ex := service.AllService.ServerCmdService.Info(f.Id)
|
||||
if ex.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
|
||||
err := service.AllService.ServerCmdService.Delete(ex)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
func (r *Rustdesk) CmdCreate(c *gin.Context) {
|
||||
f := &model.ServerCmd{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
errList := global.Validator.ValidStruct(c, f)
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
err := service.AllService.ServerCmdService.Create(f)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func (r *Rustdesk) CmdUpdate(c *gin.Context) {
|
||||
f := &model.ServerCmd{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
errList := global.Validator.ValidStruct(c, f)
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
ex := service.AllService.ServerCmdService.Info(f.Id)
|
||||
if ex.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
err := service.AllService.ServerCmdService.Update(f)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func (r *Rustdesk) SendCmd(c *gin.Context) {
|
||||
rc := &RustdeskCmd{}
|
||||
if err := c.ShouldBindJSON(rc); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
if rc.Cmd == "" {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
if rc.Target == "" {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
if rc.Target != model.ServerCmdTargetIdServer && rc.Target != model.ServerCmdTargetRelayServer {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
res, err := service.AllService.ServerCmdService.SendCmd(rc.Target, rc.Cmd, rc.Option)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, res)
|
||||
}
|
||||
|
||||
105
http/controller/admin/shareRecord.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"Gwen/global"
|
||||
"Gwen/http/request/admin"
|
||||
"Gwen/http/response"
|
||||
"Gwen/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ShareRecord struct {
|
||||
}
|
||||
|
||||
// List 列表
|
||||
// @Tags 分享记录
|
||||
// @Summary 分享记录列表
|
||||
// @Description 分享记录列表
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param user_id query int false "用户ID"
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "页大小"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/share_record/list [get]
|
||||
// @Security token
|
||||
func (sr *ShareRecord) List(c *gin.Context) {
|
||||
query := &admin.ShareRecordQuery{}
|
||||
if err := c.ShouldBindQuery(query); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
res := service.AllService.ShareRecordService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
|
||||
if query.UserId > 0 {
|
||||
tx.Where("user_id = ?", query.UserId)
|
||||
}
|
||||
})
|
||||
response.Success(c, res)
|
||||
}
|
||||
|
||||
// Delete 删除
|
||||
// @Tags 分享记录
|
||||
// @Summary 分享记录删除
|
||||
// @Description 分享记录删除
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body admin.ShareRecordForm true "分享记录信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/share_record/delete [post]
|
||||
// @Security token
|
||||
func (sr *ShareRecord) Delete(c *gin.Context) {
|
||||
f := &admin.ShareRecordForm{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
id := f.Id
|
||||
errList := global.Validator.ValidVar(c, id, "required,gt=0")
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
i := service.AllService.ShareRecordService.InfoById(f.Id)
|
||||
if i.Id > 0 {
|
||||
err := service.AllService.ShareRecordService.Delete(i)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
}
|
||||
|
||||
// BatchDelete 批量删除
|
||||
// @Tags 分享记录
|
||||
// @Summary 批量分享记录
|
||||
// @Description 批量分享记录
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body admin.PeerShareRecordBatchDeleteForm true "id"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/share_record/batchDelete [post]
|
||||
// @Security token
|
||||
func (sr *ShareRecord) BatchDelete(c *gin.Context) {
|
||||
f := &admin.PeerShareRecordBatchDeleteForm{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
if len(f.Ids) == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
err := service.AllService.ShareRecordService.BatchDelete(f.Ids)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
@@ -64,9 +64,9 @@ func (ct *Tag) Create(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
t := f.ToTag()
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 {
|
||||
t.UserId = u.Id
|
||||
if t.UserId == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
err := service.AllService.TagService.Create(t)
|
||||
if err != nil {
|
||||
@@ -96,10 +96,6 @@ func (ct *Tag) List(c *gin.Context) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
|
||||
query.UserId = int(u.Id)
|
||||
}
|
||||
res := service.AllService.TagService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
|
||||
tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB {
|
||||
return txc.Select("id,name")
|
||||
@@ -140,12 +136,12 @@ func (ct *Tag) Update(c *gin.Context) {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
t := f.ToTag()
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
ex := service.AllService.TagService.InfoById(f.Id)
|
||||
if ex.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
t := f.ToTag()
|
||||
err := service.AllService.TagService.Update(t)
|
||||
if err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
|
||||
@@ -177,20 +173,15 @@ func (ct *Tag) Delete(c *gin.Context) {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
t := service.AllService.TagService.InfoById(f.Id)
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
ex := service.AllService.TagService.InfoById(f.Id)
|
||||
if ex.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
if u.Id > 0 {
|
||||
err := service.AllService.TagService.Delete(t)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, err.Error())
|
||||
err := service.AllService.TagService.Delete(ex)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
response.Fail(c, 101, err.Error())
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"Gwen/http/request/admin"
|
||||
"Gwen/http/response"
|
||||
adResp "Gwen/http/response/admin"
|
||||
"Gwen/model"
|
||||
"Gwen/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
@@ -215,12 +216,7 @@ func (ct *User) Current(c *gin.Context) {
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
token, _ := c.Get("token")
|
||||
t := token.(string)
|
||||
response.Success(c, &adResp.LoginPayload{
|
||||
Token: t,
|
||||
Username: u.Username,
|
||||
RouteNames: service.AllService.UserService.RouteNames(u),
|
||||
Nickname: u.Nickname,
|
||||
})
|
||||
responseLoginSuccess(c, u, t)
|
||||
}
|
||||
|
||||
// ChangeCurPwd 修改当前用户密码
|
||||
@@ -247,10 +243,14 @@ func (ct *User) ChangeCurPwd(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword)
|
||||
if u.Password != oldPwd {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError"))
|
||||
return
|
||||
// If the password is not empty, the old password is verified
|
||||
// otherwise, the old password is not verified
|
||||
if !service.AllService.UserService.IsPasswordEmptyByUser(u) {
|
||||
oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword)
|
||||
if u.Password != oldPwd {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError"))
|
||||
return
|
||||
}
|
||||
}
|
||||
err := service.AllService.UserService.UpdatePassword(u, f.NewPassword)
|
||||
if err != nil {
|
||||
@@ -281,10 +281,10 @@ func (ct *User) MyOauth(c *gin.Context) {
|
||||
var res []*adResp.UserOauthItem
|
||||
for _, oa := range oal.Oauths {
|
||||
item := &adResp.UserOauthItem{
|
||||
ThirdType: oa.Op,
|
||||
Op: oa.Op,
|
||||
}
|
||||
for _, ut := range uts {
|
||||
if ut.ThirdType == oa.Op {
|
||||
if ut.Op == oa.Op {
|
||||
item.Status = 1
|
||||
break
|
||||
}
|
||||
@@ -323,3 +323,35 @@ func (ct *User) GroupUsers(c *gin.Context) {
|
||||
}
|
||||
response.Success(c, data)
|
||||
}
|
||||
|
||||
// Register
|
||||
func (ct *User) Register(c *gin.Context) {
|
||||
if !global.Config.App.Register {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "RegisterClosed"))
|
||||
return
|
||||
}
|
||||
f := &admin.RegisterForm{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
errList := global.Validator.ValidStruct(c, f)
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
u := service.AllService.UserService.Register(f.Username, f.Email, f.Password)
|
||||
if u == nil || u.Id == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed"))
|
||||
return
|
||||
}
|
||||
// 注册成功后自动登录
|
||||
ut := service.AllService.UserService.Login(u, &model.LoginLog{
|
||||
UserId: u.Id,
|
||||
Client: model.LoginLogClientWebAdmin,
|
||||
Uuid: "",
|
||||
Ip: c.ClientIP(),
|
||||
Type: model.LoginLogTypeAccount,
|
||||
})
|
||||
responseLoginSuccess(c, u, ut.Token)
|
||||
}
|
||||
|
||||
113
http/controller/admin/userToken.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"Gwen/global"
|
||||
"Gwen/http/request/admin"
|
||||
"Gwen/http/response"
|
||||
"Gwen/model"
|
||||
"Gwen/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserToken struct {
|
||||
}
|
||||
|
||||
// List 列表
|
||||
// @Tags 登录凭证
|
||||
// @Summary 登录凭证列表
|
||||
// @Description 登录凭证列表
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "页大小"
|
||||
// @Param user_id query int false "用户ID"
|
||||
// @Success 200 {object} response.Response{data=model.UserTokenList}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/user_token/list [get]
|
||||
// @Security token
|
||||
func (ct *UserToken) List(c *gin.Context) {
|
||||
query := &admin.LoginTokenQuery{}
|
||||
if err := c.ShouldBindQuery(query); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
res := service.AllService.UserService.TokenList(query.Page, query.PageSize, func(tx *gorm.DB) {
|
||||
if query.UserId > 0 {
|
||||
tx.Where("user_id = ?", query.UserId)
|
||||
}
|
||||
tx.Order("id desc")
|
||||
})
|
||||
response.Success(c, res)
|
||||
}
|
||||
|
||||
// Delete 删除
|
||||
// @Tags 登录凭证
|
||||
// @Summary 登录凭证删除
|
||||
// @Description 登录凭证删除
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body model.UserToken true "登录凭证信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/user_token/delete [post]
|
||||
// @Security token
|
||||
func (ct *UserToken) Delete(c *gin.Context) {
|
||||
f := &model.UserToken{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
id := f.Id
|
||||
errList := global.Validator.ValidVar(c, id, "required,gt=0")
|
||||
if len(errList) > 0 {
|
||||
response.Fail(c, 101, errList[0])
|
||||
return
|
||||
}
|
||||
l := service.AllService.UserService.TokenInfoById(f.Id)
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
if !service.AllService.UserService.IsAdmin(u) && l.UserId != u.Id {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
|
||||
return
|
||||
}
|
||||
if l.Id > 0 {
|
||||
err := service.AllService.UserService.DeleteToken(l)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, err.Error())
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
|
||||
}
|
||||
|
||||
// BatchDelete 批量删除
|
||||
// @Tags 登录凭证
|
||||
// @Summary 登录凭证批量删除
|
||||
// @Description 登录凭证批量删除
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body admin.UserTokenBatchDeleteForm true "登录凭证信息"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /admin/user_token/batchDelete [post]
|
||||
// @Security token
|
||||
func (ct *UserToken) BatchDelete(c *gin.Context) {
|
||||
f := &admin.UserTokenBatchDeleteForm{}
|
||||
if err := c.ShouldBindJSON(f); err != nil {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
ids := f.Ids
|
||||
if len(ids) == 0 {
|
||||
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
err := service.AllService.UserService.BatchDeleteUserToken(ids)
|
||||
if err == nil {
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
response.Fail(c, 101, err.Error())
|
||||
}
|
||||
@@ -32,8 +32,8 @@ type Ab struct {
|
||||
func (a *Ab) Ab(c *gin.Context) {
|
||||
user := service.AllService.UserService.CurUser(c)
|
||||
|
||||
al := service.AllService.AddressBookService.ListByUserId(user.Id, 1, 1000)
|
||||
tags := service.AllService.TagService.ListByUserId(user.Id)
|
||||
al := service.AllService.AddressBookService.ListByUserIdAndCollectionId(user.Id, 0, 1, 1000)
|
||||
tags := service.AllService.TagService.ListByUserIdAndCollectionId(user.Id, 0)
|
||||
|
||||
tagColors := map[string]uint{}
|
||||
//将tags中的name转成一个以逗号分割的字符串
|
||||
@@ -98,23 +98,6 @@ func (a *Ab) UpAb(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, nil)
|
||||
}
|
||||
|
||||
// Tags
|
||||
// @Tags 地址
|
||||
// @Summary 标签
|
||||
// @Description 标签
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} []model.Tag
|
||||
// @Failure 500 {object} response.ErrorResponse
|
||||
// @Router /tags [post]
|
||||
// @Security BearerAuth
|
||||
func (a *Ab) Tags(c *gin.Context) {
|
||||
user := service.AllService.UserService.CurUser(c)
|
||||
|
||||
tags := service.AllService.TagService.ListByUserId(user.Id)
|
||||
c.JSON(http.StatusOK, tags.Tags)
|
||||
}
|
||||
|
||||
// PTags
|
||||
// @Tags 地址[Personal]
|
||||
// @Summary 标签
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"Gwen/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -61,3 +62,25 @@ func (i *Index) Heartbeat(c *gin.Context) {
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{})
|
||||
}
|
||||
|
||||
// Version 版本
|
||||
// @Tags 首页
|
||||
// @Summary 版本
|
||||
// @Description 版本
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /version [get]
|
||||
func (i *Index) Version(c *gin.Context) {
|
||||
//读取resources/version文件
|
||||
v, err := os.ReadFile("resources/version")
|
||||
if err != nil {
|
||||
response.Fail(c, 101, err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(
|
||||
c,
|
||||
string(v),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -54,12 +54,13 @@ func (l *Login) Login(c *gin.Context) {
|
||||
//根据refer判断是webclient还是app
|
||||
ref := c.GetHeader("referer")
|
||||
if ref != "" {
|
||||
f.DeviceInfo.Type = "webclient"
|
||||
f.DeviceInfo.Type = model.LoginLogClientWeb
|
||||
}
|
||||
|
||||
ut := service.AllService.UserService.Login(u, &model.LoginLog{
|
||||
UserId: u.Id,
|
||||
Client: f.DeviceInfo.Type,
|
||||
DeviceId: f.Id,
|
||||
Uuid: f.Uuid,
|
||||
Ip: c.ClientIP(),
|
||||
Type: model.LoginLogTypeAccount,
|
||||
@@ -83,22 +84,10 @@ func (l *Login) Login(c *gin.Context) {
|
||||
// @Failure 500 {object} response.ErrorResponse
|
||||
// @Router /login-options [get]
|
||||
func (l *Login) LoginOptions(c *gin.Context) {
|
||||
oauthOks := []string{}
|
||||
err, _ := service.AllService.OauthService.GetOauthConfig(model.OauthTypeGithub)
|
||||
if err == nil {
|
||||
oauthOks = append(oauthOks, model.OauthTypeGithub)
|
||||
}
|
||||
err, _ = service.AllService.OauthService.GetOauthConfig(model.OauthTypeGoogle)
|
||||
if err == nil {
|
||||
oauthOks = append(oauthOks, model.OauthTypeGoogle)
|
||||
}
|
||||
err, _ = service.AllService.OauthService.GetOauthConfig(model.OauthTypeOidc)
|
||||
if err == nil {
|
||||
oauthOks = append(oauthOks, model.OauthTypeOidc)
|
||||
}
|
||||
oauthOks = append(oauthOks, model.OauthTypeWebauth)
|
||||
ops := service.AllService.OauthService.GetOauthProviders()
|
||||
ops = append(ops, model.OauthTypeWebauth)
|
||||
var oidcItems []map[string]string
|
||||
for _, v := range oauthOks {
|
||||
for _, v := range ops {
|
||||
oidcItems = append(oidcItems, map[string]string{"name": v})
|
||||
}
|
||||
common, err := json.Marshal(oidcItems)
|
||||
@@ -108,7 +97,7 @@ func (l *Login) LoginOptions(c *gin.Context) {
|
||||
}
|
||||
var res []string
|
||||
res = append(res, "common-oidc/"+string(common))
|
||||
for _, v := range oauthOks {
|
||||
for _, v := range ops {
|
||||
res = append(res, "oidc/"+v)
|
||||
}
|
||||
c.JSON(http.StatusOK, res)
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
"Gwen/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Oauth struct {
|
||||
@@ -32,13 +30,11 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
|
||||
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
|
||||
return
|
||||
}
|
||||
//fmt.Println(f)
|
||||
if f.Op != model.OauthTypeWebauth && f.Op != model.OauthTypeGoogle && f.Op != model.OauthTypeGithub && f.Op != model.OauthTypeOidc {
|
||||
response.Error(c, response.TranslateMsg(c, "ParamsError"))
|
||||
return
|
||||
}
|
||||
|
||||
err, code, url := service.AllService.OauthService.BeginAuth(f.Op)
|
||||
oauthService := service.AllService.OauthService
|
||||
var code string
|
||||
var url string
|
||||
err, code, url = oauthService.BeginAuth(f.Op)
|
||||
if err != nil {
|
||||
response.Error(c, response.TranslateMsg(c, err.Error()))
|
||||
return
|
||||
@@ -98,6 +94,7 @@ func (o *Oauth) OidcAuthQueryPre(c *gin.Context) (*model.User, *model.UserToken)
|
||||
ut = service.AllService.UserService.Login(u, &model.LoginLog{
|
||||
UserId: u.Id,
|
||||
Client: v.DeviceType,
|
||||
DeviceId: v.Id,
|
||||
Uuid: v.Uuid,
|
||||
Ip: c.ClientIP(),
|
||||
Type: model.LoginLogTypeOauth,
|
||||
@@ -149,70 +146,43 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
|
||||
c.String(http.StatusInternalServerError, response.TranslateParamMsg(c, "ParamIsEmpty", "state"))
|
||||
return
|
||||
}
|
||||
|
||||
cacheKey := state
|
||||
oauthService := service.AllService.OauthService
|
||||
//从缓存中获取
|
||||
v := service.AllService.OauthService.GetOauthCache(cacheKey)
|
||||
if v == nil {
|
||||
oauthCache := oauthService.GetOauthCache(cacheKey)
|
||||
if oauthCache == nil {
|
||||
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthExpired"))
|
||||
return
|
||||
}
|
||||
|
||||
ty := v.Op
|
||||
ac := v.Action
|
||||
var u *model.User
|
||||
openid := ""
|
||||
thirdName := ""
|
||||
//fmt.Println("ty ac ", ty, ac)
|
||||
|
||||
if ty == model.OauthTypeGithub {
|
||||
code := c.Query("code")
|
||||
err, userData := service.AllService.OauthService.GithubCallback(code)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
|
||||
return
|
||||
}
|
||||
openid = strconv.Itoa(userData.Id)
|
||||
thirdName = userData.Login
|
||||
} else if ty == model.OauthTypeGoogle {
|
||||
code := c.Query("code")
|
||||
err, userData := service.AllService.OauthService.GoogleCallback(code)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
|
||||
return
|
||||
}
|
||||
openid = userData.Email
|
||||
//将空格替换成_
|
||||
thirdName = strings.Replace(userData.Name, " ", "_", -1)
|
||||
} else if ty == model.OauthTypeOidc {
|
||||
code := c.Query("code")
|
||||
err, userData := service.AllService.OauthService.OidcCallback(code)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
|
||||
return
|
||||
}
|
||||
openid = userData.Sub
|
||||
thirdName = userData.PreferredUsername
|
||||
} else {
|
||||
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ParamsError"))
|
||||
op := oauthCache.Op
|
||||
action := oauthCache.Action
|
||||
var user *model.User
|
||||
// 获取用户信息
|
||||
code := c.Query("code")
|
||||
err, oauthUser := oauthService.Callback(code, op)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
|
||||
return
|
||||
}
|
||||
if ac == service.OauthActionTypeBind {
|
||||
userId := oauthCache.UserId
|
||||
openid := oauthUser.OpenId
|
||||
if action == service.OauthActionTypeBind {
|
||||
|
||||
//fmt.Println("bind", ty, userData)
|
||||
utr := service.AllService.OauthService.UserThirdInfo(ty, openid)
|
||||
// 检查此openid是否已经绑定过
|
||||
utr := oauthService.UserThirdInfo(op, openid)
|
||||
if utr.UserId > 0 {
|
||||
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBindOtherUser"))
|
||||
return
|
||||
}
|
||||
//绑定
|
||||
u = service.AllService.UserService.InfoById(v.UserId)
|
||||
if u == nil {
|
||||
user = service.AllService.UserService.InfoById(userId)
|
||||
if user == nil {
|
||||
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
|
||||
return
|
||||
}
|
||||
//绑定
|
||||
err := service.AllService.OauthService.BindOauthUser(ty, openid, thirdName, v.UserId)
|
||||
err := oauthService.BindOauthUser(userId, oauthUser, op)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail"))
|
||||
return
|
||||
@@ -220,42 +190,41 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
|
||||
c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess"))
|
||||
return
|
||||
|
||||
} else if ac == service.OauthActionTypeLogin {
|
||||
} else if action == service.OauthActionTypeLogin {
|
||||
//登录
|
||||
if v.UserId != 0 {
|
||||
if userId != 0 {
|
||||
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
|
||||
return
|
||||
}
|
||||
u = service.AllService.UserService.InfoByGithubId(openid)
|
||||
if u == nil {
|
||||
oa := service.AllService.OauthService.InfoByOp(ty)
|
||||
if !*oa.AutoRegister {
|
||||
user = service.AllService.UserService.InfoByOauthId(op, openid)
|
||||
if user == nil {
|
||||
oauthConfig := oauthService.InfoByOp(op)
|
||||
if !*oauthConfig.AutoRegister {
|
||||
//c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定")
|
||||
v.ThirdName = thirdName
|
||||
v.ThirdOpenId = openid
|
||||
oauthCache.UpdateFromOauthUser(oauthUser)
|
||||
url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey
|
||||
c.Redirect(http.StatusFound, url)
|
||||
return
|
||||
}
|
||||
|
||||
//自动注册
|
||||
u = service.AllService.UserService.RegisterByOauth(ty, thirdName, openid)
|
||||
if u.Id == 0 {
|
||||
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthRegisterFailed"))
|
||||
err, user = service.AllService.UserService.RegisterByOauth(oauthUser, op)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, response.TranslateMsg(c, err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
v.UserId = u.Id
|
||||
service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
|
||||
oauthCache.UserId = user.Id
|
||||
oauthService.SetOauthCache(cacheKey, oauthCache, 0)
|
||||
// 如果是webadmin,登录成功后跳转到webadmin
|
||||
if v.DeviceType == "webadmin" {
|
||||
if oauthCache.DeviceType == model.LoginLogClientWebAdmin {
|
||||
/*service.AllService.UserService.Login(u, &model.LoginLog{
|
||||
UserId: u.Id,
|
||||
Client: "webadmin",
|
||||
Uuid: "", //must be empty
|
||||
Ip: c.ClientIP(),
|
||||
Type: model.LoginLogTypeOauth,
|
||||
Platform: v.DeviceOs,
|
||||
Platform: oauthService.DeviceOs,
|
||||
})*/
|
||||
url := global.Config.Rustdesk.ApiServer + "/_admin/#/"
|
||||
c.Redirect(http.StatusFound, url)
|
||||
|
||||
@@ -34,7 +34,7 @@ type User struct {
|
||||
// @Produce json
|
||||
// @Success 200 {object} apiResp.UserPayload
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /api [get]
|
||||
// @Router /currentUser [get]
|
||||
// @Security token
|
||||
func (u *User) Info(c *gin.Context) {
|
||||
user := service.AllService.UserService.CurUser(c)
|
||||
|
||||
@@ -26,7 +26,7 @@ func (i *WebClient) ServerConfig(c *gin.Context) {
|
||||
u := service.AllService.UserService.CurUser(c)
|
||||
|
||||
peers := map[string]*api.WebClientPeerPayload{}
|
||||
abs := service.AllService.AddressBookService.ListByUserId(u.Id, 1, 100)
|
||||
abs := service.AllService.AddressBookService.ListByUserIdAndCollectionId(u.Id, 0, 1, 100)
|
||||
for _, ab := range abs.AddressBooks {
|
||||
pp := &api.WebClientPeerPayload{}
|
||||
pp.FromAddressBook(ab)
|
||||
@@ -64,12 +64,15 @@ func (i *WebClient) SharedPeer(c *gin.Context) {
|
||||
response.Fail(c, 101, "share not found")
|
||||
return
|
||||
}
|
||||
//判断是否过期,created_at + expire > now
|
||||
ca := time.Time(sr.CreatedAt)
|
||||
if ca.Add(time.Second * time.Duration(sr.Expire)).Before(time.Now()) {
|
||||
response.Fail(c, 101, "share expired")
|
||||
return
|
||||
if sr.Expire != 0 {
|
||||
//判断是否过期,created_at + expire > now
|
||||
ca := time.Time(sr.CreatedAt)
|
||||
if ca.Add(time.Second * time.Duration(sr.Expire)).Before(time.Now()) {
|
||||
response.Fail(c, 101, "share expired")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ab := service.AllService.AddressBookService.InfoByUserIdAndId(sr.UserId, sr.PeerId)
|
||||
if ab.RowId == 0 {
|
||||
response.Fail(c, 101, "peer not found")
|
||||
@@ -85,3 +88,23 @@ func (i *WebClient) SharedPeer(c *gin.Context) {
|
||||
"peer": pp,
|
||||
})
|
||||
}
|
||||
|
||||
// ServerConfigV2 服务配置
|
||||
// @Tags WEBCLIENT_V2
|
||||
// @Summary 服务配置
|
||||
// @Description 服务配置,给webclient提供api-server
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Router /server-config-v2 [get]
|
||||
// @Security token
|
||||
func (i *WebClient) ServerConfigV2(c *gin.Context) {
|
||||
response.Success(
|
||||
c,
|
||||
gin.H{
|
||||
"id_server": global.Config.Rustdesk.IdServer,
|
||||
"key": global.Config.Rustdesk.Key,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package web
|
||||
import (
|
||||
"Gwen/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Index struct {
|
||||
@@ -14,9 +15,13 @@ func (i *Index) Index(c *gin.Context) {
|
||||
|
||||
func (i *Index) ConfigJs(c *gin.Context) {
|
||||
apiServer := global.Config.Rustdesk.ApiServer
|
||||
|
||||
magicQueryonline := strconv.Itoa(global.Config.Rustdesk.WebclientMagicQueryonline)
|
||||
tmp := `
|
||||
localStorage.setItem('api-server', "` + apiServer + `")
|
||||
`
|
||||
const ws2_prefix = 'wc-'
|
||||
localStorage.setItem(ws2_prefix+'api-server', "` + apiServer + `")
|
||||
|
||||
window.webclient_magic_queryonline = ` + magicQueryonline + ``
|
||||
|
||||
c.String(200, tmp)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ func AdminAuth() gin.HandlerFunc {
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
user := service.AllService.UserService.InfoByAccessToken(token)
|
||||
user, ut := service.AllService.UserService.InfoByAccessToken(token)
|
||||
if user.Id == 0 {
|
||||
response.Fail(c, 403, "请先登录")
|
||||
c.Abort()
|
||||
@@ -26,6 +26,8 @@ func AdminAuth() gin.HandlerFunc {
|
||||
|
||||
c.Set("curUser", user)
|
||||
c.Set("token", token)
|
||||
//如果时间小于1天,token自动续期
|
||||
service.AllService.UserService.AutoRefreshAccessToken(ut)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"Gwen/global"
|
||||
"Gwen/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -27,8 +28,22 @@ func RustAuth() gin.HandlerFunc {
|
||||
//提取token,格式是Bearer {token}
|
||||
//这里只是简单的提取
|
||||
token = token[7:]
|
||||
|
||||
//验证token
|
||||
user := service.AllService.UserService.InfoByAccessToken(token)
|
||||
|
||||
//检查是否设置了jwt key
|
||||
if len(global.Jwt.Key) > 0 {
|
||||
uid, _ := service.AllService.UserService.VerifyJWT(token)
|
||||
if uid == 0 {
|
||||
c.JSON(401, gin.H{
|
||||
"error": "Unauthorized",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
user, ut := service.AllService.UserService.InfoByAccessToken(token)
|
||||
if user.Id == 0 {
|
||||
c.JSON(401, gin.H{
|
||||
"error": "Unauthorized",
|
||||
@@ -38,7 +53,7 @@ func RustAuth() gin.HandlerFunc {
|
||||
}
|
||||
if !service.AllService.UserService.CheckUserEnable(user) {
|
||||
c.JSON(401, gin.H{
|
||||
"error": "账号已被禁用",
|
||||
"error": "Unauthorized",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
@@ -46,6 +61,9 @@ func RustAuth() gin.HandlerFunc {
|
||||
|
||||
c.Set("curUser", user)
|
||||
c.Set("token", token)
|
||||
|
||||
service.AllService.UserService.AutoRefreshAccessToken(ut)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,3 +122,14 @@ type AddressBookCollectionRuleQuery struct {
|
||||
IsMy int `form:"is_my"`
|
||||
PageQuery
|
||||
}
|
||||
|
||||
type BatchCreateFromPeersForm struct {
|
||||
CollectionId uint `json:"collection_id"`
|
||||
PeerIds []uint `json:"peer_ids"`
|
||||
Tags []string `json:"tags"`
|
||||
UserId uint `json:"user_id"`
|
||||
}
|
||||
type BatchUpdateTagsForm struct {
|
||||
RowIds []uint `json:"row_ids"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
@@ -5,3 +5,10 @@ type AuditQuery struct {
|
||||
FromPeer string `form:"from_peer"`
|
||||
PageQuery
|
||||
}
|
||||
|
||||
type AuditConnLogIds struct {
|
||||
Ids []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
type AuditFileLogIds struct {
|
||||
Ids []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ type Login struct {
|
||||
Username string `json:"username" validate:"required" label:"用户名"`
|
||||
Password string `json:"password,omitempty" validate:"required" label:"密码"`
|
||||
Platform string `json:"platform" label:"平台"`
|
||||
Captcha string `json:"captcha,omitempty" label:"验证码"`
|
||||
}
|
||||
|
||||
type LoginLogQuery struct {
|
||||
@@ -11,3 +12,11 @@ type LoginLogQuery struct {
|
||||
IsMy int `form:"is_my"`
|
||||
PageQuery
|
||||
}
|
||||
type LoginTokenQuery struct {
|
||||
UserId int `form:"user_id"`
|
||||
PageQuery
|
||||
}
|
||||
|
||||
type LoginLogIds struct {
|
||||
Ids []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package admin
|
||||
|
||||
import "Gwen/model"
|
||||
import (
|
||||
"Gwen/model"
|
||||
)
|
||||
|
||||
type BindOauthForm struct {
|
||||
Op string `json:"op" binding:"required"`
|
||||
@@ -13,19 +15,21 @@ type UnBindOauthForm struct {
|
||||
Op string `json:"op" binding:"required"`
|
||||
}
|
||||
type OauthForm struct {
|
||||
Id uint `json:"id"`
|
||||
Op string `json:"op" validate:"required"`
|
||||
Issuer string `json:"issuer" validate:"omitempty,url"`
|
||||
Scopes string `json:"scopes" validate:"omitempty"`
|
||||
ClientId string `json:"client_id" validate:"required"`
|
||||
ClientSecret string `json:"client_secret" validate:"required"`
|
||||
RedirectUrl string `json:"redirect_url" validate:"required"`
|
||||
AutoRegister *bool `json:"auto_register"`
|
||||
Id uint `json:"id"`
|
||||
Op string `json:"op" validate:"omitempty"`
|
||||
OauthType string `json:"oauth_type" validate:"required"`
|
||||
Issuer string `json:"issuer" validate:"omitempty,url"`
|
||||
Scopes string `json:"scopes" validate:"omitempty"`
|
||||
ClientId string `json:"client_id" validate:"required"`
|
||||
ClientSecret string `json:"client_secret" validate:"required"`
|
||||
RedirectUrl string `json:"redirect_url" validate:"required"`
|
||||
AutoRegister *bool `json:"auto_register"`
|
||||
}
|
||||
|
||||
func (of *OauthForm) ToOauth() *model.Oauth {
|
||||
oa := &model.Oauth{
|
||||
Op: of.Op,
|
||||
OauthType: of.OauthType,
|
||||
ClientId: of.ClientId,
|
||||
ClientSecret: of.ClientSecret,
|
||||
RedirectUrl: of.RedirectUrl,
|
||||
|
||||
@@ -38,6 +38,7 @@ type PeerQuery struct {
|
||||
TimeAgo int `json:"time_ago" form:"time_ago"`
|
||||
Id string `json:"id" form:"id"`
|
||||
Hostname string `json:"hostname" form:"hostname"`
|
||||
Uuids string `json:"uuids" form:"uuids"`
|
||||
}
|
||||
|
||||
type SimpleDataQuery struct {
|
||||
|
||||
15
http/request/admin/shareRecord.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package admin
|
||||
|
||||
type ShareRecordQuery struct {
|
||||
UserId uint `json:"user_id" form:"user_id"`
|
||||
PageQuery
|
||||
}
|
||||
|
||||
type ShareRecordForm struct {
|
||||
Id uint `json:"id" form:"id"`
|
||||
UserId uint `json:"user_id" form:"user_id"`
|
||||
}
|
||||
|
||||
type PeerShareRecordBatchDeleteForm struct {
|
||||
Ids []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
@@ -6,7 +6,8 @@ import (
|
||||
|
||||
type UserForm struct {
|
||||
Id uint `json:"id"`
|
||||
Username string `json:"username" validate:"required,gte=4,lte=10"`
|
||||
Username string `json:"username" validate:"required,gte=2,lte=32"`
|
||||
Email string `json:"email"` //validate:"required,email" email不强制
|
||||
//Password string `json:"password" validate:"required,gte=4,lte=20"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
@@ -19,6 +20,7 @@ func (uf *UserForm) FromUser(user *model.User) *UserForm {
|
||||
uf.Id = user.Id
|
||||
uf.Username = user.Username
|
||||
uf.Nickname = user.Nickname
|
||||
uf.Email = user.Email
|
||||
uf.Avatar = user.Avatar
|
||||
uf.GroupId = user.GroupId
|
||||
uf.IsAdmin = user.IsAdmin
|
||||
@@ -30,6 +32,7 @@ func (uf *UserForm) ToUser() *model.User {
|
||||
user.Id = uf.Id
|
||||
user.Username = uf.Username
|
||||
user.Nickname = uf.Nickname
|
||||
user.Email = uf.Email
|
||||
user.Avatar = uf.Avatar
|
||||
user.GroupId = uf.GroupId
|
||||
user.IsAdmin = uf.IsAdmin
|
||||
@@ -48,14 +51,25 @@ type UserQuery struct {
|
||||
}
|
||||
type UserPasswordForm struct {
|
||||
Id uint `json:"id" validate:"required"`
|
||||
Password string `json:"password" validate:"required,gte=4,lte=20"`
|
||||
Password string `json:"password" validate:"required,gte=4,lte=32"`
|
||||
}
|
||||
|
||||
type ChangeCurPasswordForm struct {
|
||||
OldPassword string `json:"old_password" validate:"required,gte=4,lte=20"`
|
||||
NewPassword string `json:"new_password" validate:"required,gte=4,lte=20"`
|
||||
OldPassword string `json:"old_password" validate:"required,gte=4,lte=32"`
|
||||
NewPassword string `json:"new_password" validate:"required,gte=4,lte=32"`
|
||||
}
|
||||
type GroupUsersQuery struct {
|
||||
IsMy int `json:"is_my"`
|
||||
UserId uint `json:"user_id"`
|
||||
}
|
||||
|
||||
type RegisterForm struct {
|
||||
Username string `json:"username" validate:"required,gte=2,lte=32"`
|
||||
Email string `json:"email"` // validate:"required,email"
|
||||
Password string `json:"password" validate:"required,gte=4,lte=32"`
|
||||
ConfirmPassword string `json:"confirm_password" validate:"required,gte=4,lte=32"`
|
||||
}
|
||||
|
||||
type UserTokenBatchDeleteForm struct {
|
||||
Ids []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ type LoginForm struct {
|
||||
Id string `json:"id" label:"id"`
|
||||
Type string `json:"type" label:"type"`
|
||||
Uuid string `json:"uuid" label:"uuid"`
|
||||
Username string `json:"username" validate:"required,gte=4,lte=10" label:"用户名"`
|
||||
Password string `json:"password,omitempty" validate:"gte=4,lte=20" label:"密码"`
|
||||
Username string `json:"username" validate:"required,gte=2,lte=32" label:"用户名"`
|
||||
Password string `json:"password,omitempty" validate:"gte=4,lte=32" label:"密码"`
|
||||
}
|
||||
|
||||
type UserListQuery struct {
|
||||
|
||||
@@ -4,19 +4,23 @@ import "Gwen/model"
|
||||
|
||||
type LoginPayload struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar"`
|
||||
Token string `json:"token"`
|
||||
RouteNames []string `json:"route_names"`
|
||||
Nickname string `json:"nickname"`
|
||||
}
|
||||
|
||||
var UserRouteNames = []string{
|
||||
"MyTagList", "MyAddressBookList", "MyInfo", "MyAddressBookCollection",
|
||||
func (lp *LoginPayload) FromUser(user *model.User) {
|
||||
lp.Username = user.Username
|
||||
lp.Email = user.Email
|
||||
lp.Avatar = user.Avatar
|
||||
lp.Nickname = user.Nickname
|
||||
}
|
||||
var AdminRouteNames = []string{"*"}
|
||||
|
||||
type UserOauthItem struct {
|
||||
ThirdType string `json:"third_type"`
|
||||
Status int `json:"status"`
|
||||
Op string `json:"op"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
type GroupUsersPayload struct {
|
||||
|
||||
@@ -29,6 +29,7 @@ type UserPayload struct {
|
||||
|
||||
func (up *UserPayload) FromUser(user *model.User) *UserPayload {
|
||||
up.Name = user.Username
|
||||
up.Email = user.Email
|
||||
up.IsAdmin = user.IsAdmin
|
||||
up.Status = int(user.Status)
|
||||
up.Info = map[string]interface{}{}
|
||||
|
||||
@@ -2,7 +2,9 @@ package router
|
||||
|
||||
import (
|
||||
_ "Gwen/docs/admin"
|
||||
"Gwen/global"
|
||||
"Gwen/http/controller/admin"
|
||||
"Gwen/http/controller/admin/my"
|
||||
"Gwen/http/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
@@ -13,11 +15,13 @@ func Init(g *gin.Engine) {
|
||||
|
||||
//swagger
|
||||
//g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
g.GET("/admin/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("admin")))
|
||||
if global.Config.App.ShowSwagger == 1 {
|
||||
g.GET("/admin/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("admin")))
|
||||
}
|
||||
|
||||
adg := g.Group("/api/admin")
|
||||
LoginBind(adg)
|
||||
|
||||
adg.POST("/user/register", (&admin.User{}).Register)
|
||||
adg.Use(middleware.AdminAuth())
|
||||
//FileBind(adg)
|
||||
UserBind(adg)
|
||||
@@ -30,15 +34,36 @@ func Init(g *gin.Engine) {
|
||||
AuditBind(adg)
|
||||
AddressBookCollectionBind(adg)
|
||||
AddressBookCollectionRuleBind(adg)
|
||||
rs := &admin.Rustdesk{}
|
||||
adg.GET("/server-config", rs.ServerConfig)
|
||||
adg.GET("/app-config", rs.AppConfig)
|
||||
UserTokenBind(adg)
|
||||
ConfigBind(adg)
|
||||
|
||||
//deprecated by ConfigBind
|
||||
//rs := &admin.Rustdesk{}
|
||||
//adg.GET("/server-config", rs.ServerConfig)
|
||||
//adg.GET("/app-config", rs.AppConfig)
|
||||
//deprecated end
|
||||
|
||||
ShareRecordBind(adg)
|
||||
MyBind(adg)
|
||||
|
||||
RustdeskCmdBind(adg)
|
||||
|
||||
//访问静态文件
|
||||
//g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload"))
|
||||
}
|
||||
|
||||
func RustdeskCmdBind(adg *gin.RouterGroup) {
|
||||
cont := &admin.Rustdesk{}
|
||||
rg := adg.Group("/rustdesk")
|
||||
rg.POST("/sendCmd", cont.SendCmd)
|
||||
rg.GET("/cmdList", cont.CmdList)
|
||||
rg.POST("/cmdDelete", cont.CmdDelete)
|
||||
rg.POST("/cmdCreate", cont.CmdCreate)
|
||||
}
|
||||
func LoginBind(rg *gin.RouterGroup) {
|
||||
cont := &admin.Login{}
|
||||
rg.POST("/login", cont.Login)
|
||||
rg.GET("/captcha", cont.Captcha)
|
||||
rg.POST("/logout", cont.Logout)
|
||||
rg.GET("/login-options", cont.LoginOptions)
|
||||
rg.POST("/oidc/auth", cont.OidcAuth)
|
||||
@@ -52,6 +77,7 @@ func UserBind(rg *gin.RouterGroup) {
|
||||
aR.GET("/current", cont.Current)
|
||||
aR.POST("/changeCurPwd", cont.ChangeCurPwd)
|
||||
aR.POST("/myOauth", cont.MyOauth)
|
||||
//aR.GET("/myPeer", cont.MyPeer)
|
||||
aR.POST("/groupUsers", cont.GroupUsers)
|
||||
}
|
||||
aRP := rg.Group("/user").Use(middleware.AdminPrivilege())
|
||||
@@ -79,7 +105,7 @@ func GroupBind(rg *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
func TagBind(rg *gin.RouterGroup) {
|
||||
aR := rg.Group("/tag")
|
||||
aR := rg.Group("/tag").Use(middleware.AdminPrivilege())
|
||||
{
|
||||
cont := &admin.Tag{}
|
||||
aR.GET("/list", cont.List)
|
||||
@@ -94,19 +120,23 @@ func AddressBookBind(rg *gin.RouterGroup) {
|
||||
aR := rg.Group("/address_book")
|
||||
{
|
||||
cont := &admin.AddressBook{}
|
||||
aR.GET("/list", cont.List)
|
||||
aR.GET("/detail/:id", cont.Detail)
|
||||
aR.POST("/create", cont.Create)
|
||||
aR.POST("/update", cont.Update)
|
||||
aR.POST("/delete", cont.Delete)
|
||||
aR.POST("/shareByWebClient", cont.ShareByWebClient)
|
||||
|
||||
arp := aR.Use(middleware.AdminPrivilege())
|
||||
arp.GET("/list", cont.List)
|
||||
//arp.GET("/detail/:id", cont.Detail)
|
||||
arp.POST("/create", cont.Create)
|
||||
arp.POST("/update", cont.Update)
|
||||
arp.POST("/delete", cont.Delete)
|
||||
arp.POST("/batchCreate", cont.BatchCreate)
|
||||
arp.POST("/batchCreateFromPeers", cont.BatchCreateFromPeers)
|
||||
|
||||
}
|
||||
}
|
||||
func PeerBind(rg *gin.RouterGroup) {
|
||||
aR := rg.Group("/peer")
|
||||
aR.POST("/simpleData", (&admin.Peer{}).SimpleData)
|
||||
aR.Use(middleware.AdminPrivilege())
|
||||
{
|
||||
cont := &admin.Peer{}
|
||||
aR.GET("/list", cont.List)
|
||||
@@ -114,10 +144,7 @@ func PeerBind(rg *gin.RouterGroup) {
|
||||
aR.POST("/create", cont.Create)
|
||||
aR.POST("/update", cont.Update)
|
||||
aR.POST("/delete", cont.Delete)
|
||||
aR.POST("/simpleData", cont.SimpleData)
|
||||
|
||||
arp := aR.Use(middleware.AdminPrivilege())
|
||||
arp.POST("/batchDelete", cont.BatchDelete)
|
||||
aR.POST("/batchDelete", cont.BatchDelete)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,22 +171,25 @@ func OauthBind(rg *gin.RouterGroup) {
|
||||
|
||||
}
|
||||
func LoginLogBind(rg *gin.RouterGroup) {
|
||||
aR := rg.Group("/login_log")
|
||||
cont := &admin.LoginLog{}
|
||||
aR := rg.Group("/login_log").Use(middleware.AdminPrivilege())
|
||||
aR.GET("/list", cont.List)
|
||||
aR.POST("/delete", cont.Delete)
|
||||
aR.POST("/batchDelete", cont.BatchDelete)
|
||||
}
|
||||
func AuditBind(rg *gin.RouterGroup) {
|
||||
cont := &admin.Audit{}
|
||||
aR := rg.Group("/audit_conn").Use(middleware.AdminPrivilege())
|
||||
aR.GET("/list", cont.ConnList)
|
||||
aR.POST("/delete", cont.ConnDelete)
|
||||
aR.POST("/batchDelete", cont.BatchConnDelete)
|
||||
afR := rg.Group("/audit_file").Use(middleware.AdminPrivilege())
|
||||
afR.GET("/list", cont.FileList)
|
||||
afR.POST("/delete", cont.FileDelete)
|
||||
afR.POST("/batchDelete", cont.BatchFileDelete)
|
||||
}
|
||||
func AddressBookCollectionBind(rg *gin.RouterGroup) {
|
||||
aR := rg.Group("/address_book_collection")
|
||||
aR := rg.Group("/address_book_collection").Use(middleware.AdminPrivilege())
|
||||
{
|
||||
cont := &admin.AddressBookCollection{}
|
||||
aR.GET("/list", cont.List)
|
||||
@@ -171,7 +201,7 @@ func AddressBookCollectionBind(rg *gin.RouterGroup) {
|
||||
|
||||
}
|
||||
func AddressBookCollectionRuleBind(rg *gin.RouterGroup) {
|
||||
aR := rg.Group("/address_book_collection_rule")
|
||||
aR := rg.Group("/address_book_collection_rule").Use(middleware.AdminPrivilege())
|
||||
{
|
||||
cont := &admin.AddressBookCollectionRule{}
|
||||
aR.GET("/list", cont.List)
|
||||
@@ -181,6 +211,20 @@ func AddressBookCollectionRuleBind(rg *gin.RouterGroup) {
|
||||
aR.POST("/delete", cont.Delete)
|
||||
}
|
||||
}
|
||||
func UserTokenBind(rg *gin.RouterGroup) {
|
||||
aR := rg.Group("/user_token").Use(middleware.AdminPrivilege())
|
||||
cont := &admin.UserToken{}
|
||||
aR.GET("/list", cont.List)
|
||||
aR.POST("/delete", cont.Delete)
|
||||
aR.POST("/batchDelete", cont.BatchDelete)
|
||||
}
|
||||
func ConfigBind(rg *gin.RouterGroup) {
|
||||
aR := rg.Group("/config")
|
||||
rs := &admin.Config{}
|
||||
aR.GET("/server", rs.ServerConfig)
|
||||
aR.GET("/app", rs.AppConfig)
|
||||
aR.GET("/admin", rs.AdminConfig)
|
||||
}
|
||||
|
||||
/*
|
||||
func FileBind(rg *gin.RouterGroup) {
|
||||
@@ -194,3 +238,69 @@ func FileBind(rg *gin.RouterGroup) {
|
||||
aR.POST("/upload", cont.Upload)
|
||||
}
|
||||
}*/
|
||||
|
||||
func MyBind(rg *gin.RouterGroup) {
|
||||
{
|
||||
cont := &my.ShareRecord{}
|
||||
rg.GET("/my/share_record/list", cont.List)
|
||||
rg.POST("/my/share_record/delete", cont.Delete)
|
||||
rg.POST("/my/share_record/batchDelete", cont.BatchDelete)
|
||||
}
|
||||
|
||||
{
|
||||
cont := &my.AddressBook{}
|
||||
rg.GET("/my/address_book/list", cont.List)
|
||||
rg.POST("/my/address_book/create", cont.Create)
|
||||
rg.POST("/my/address_book/update", cont.Update)
|
||||
rg.POST("/my/address_book/delete", cont.Delete)
|
||||
rg.POST("/my/address_book/batchCreateFromPeers", cont.BatchCreateFromPeers)
|
||||
rg.POST("/my/address_book/batchUpdateTags", cont.BatchUpdateTags)
|
||||
}
|
||||
|
||||
{
|
||||
cont := &my.Tag{}
|
||||
rg.GET("/my/tag/list", cont.List)
|
||||
rg.POST("/my/tag/create", cont.Create)
|
||||
rg.POST("/my/tag/update", cont.Update)
|
||||
rg.POST("/my/tag/delete", cont.Delete)
|
||||
}
|
||||
|
||||
{
|
||||
cont := &my.AddressBookCollection{}
|
||||
rg.GET("/my/address_book_collection/list", cont.List)
|
||||
rg.POST("/my/address_book_collection/create", cont.Create)
|
||||
rg.POST("/my/address_book_collection/update", cont.Update)
|
||||
rg.POST("/my/address_book_collection/delete", cont.Delete)
|
||||
}
|
||||
|
||||
{
|
||||
cont := &my.AddressBookCollectionRule{}
|
||||
rg.GET("/my/address_book_collection_rule/list", cont.List)
|
||||
rg.POST("/my/address_book_collection_rule/create", cont.Create)
|
||||
rg.POST("/my/address_book_collection_rule/update", cont.Update)
|
||||
rg.POST("/my/address_book_collection_rule/delete", cont.Delete)
|
||||
}
|
||||
{
|
||||
cont := &my.Peer{}
|
||||
rg.GET("/my/peer/list", cont.List)
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
cont := &my.LoginLog{}
|
||||
rg.GET("/my/login_log/list", cont.List)
|
||||
rg.POST("/my/login_log/delete", cont.Delete)
|
||||
rg.POST("/my/login_log/batchDelete", cont.BatchDelete)
|
||||
}
|
||||
}
|
||||
|
||||
func ShareRecordBind(rg *gin.RouterGroup) {
|
||||
aR := rg.Group("/share_record").Use(middleware.AdminPrivilege())
|
||||
{
|
||||
cont := &admin.ShareRecord{}
|
||||
aR.GET("/list", cont.List)
|
||||
aR.POST("/delete", cont.Delete)
|
||||
aR.POST("/batchDelete", cont.BatchDelete)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,14 +15,19 @@ func ApiInit(g *gin.Engine) {
|
||||
|
||||
//g.Use(middleware.Cors())
|
||||
//swagger
|
||||
g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("api")))
|
||||
if global.Config.App.ShowSwagger == 1 {
|
||||
g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("api")))
|
||||
}
|
||||
|
||||
frg := g.Group("/api")
|
||||
|
||||
i := &api.Index{}
|
||||
frg.GET("/", i.Index)
|
||||
{
|
||||
i := &api.Index{}
|
||||
frg.GET("/", i.Index)
|
||||
frg.GET("/version", i.Version)
|
||||
|
||||
frg.POST("/heartbeat", i.Heartbeat)
|
||||
frg.POST("/heartbeat", i.Heartbeat)
|
||||
}
|
||||
|
||||
{
|
||||
l := &api.Login{}
|
||||
@@ -31,6 +36,7 @@ func ApiInit(g *gin.Engine) {
|
||||
frg.POST("/login", l.Login)
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
o := &api.Oauth{}
|
||||
// [method:POST] [uri:/api/oidc/auth]
|
||||
@@ -50,11 +56,15 @@ func ApiInit(g *gin.Engine) {
|
||||
if global.Config.App.WebClient == 1 {
|
||||
WebClientRoutes(frg)
|
||||
}
|
||||
au := &api.Audit{}
|
||||
//[method:POST] [uri:/api/audit/conn]
|
||||
frg.POST("/audit/conn", au.AuditConn)
|
||||
//[method:POST] [uri:/api/audit/file]
|
||||
frg.POST("/audit/file", au.AuditFile)
|
||||
|
||||
{
|
||||
au := &api.Audit{}
|
||||
//[method:POST] [uri:/api/audit/conn]
|
||||
frg.POST("/audit/conn", au.AuditConn)
|
||||
//[method:POST] [uri:/api/audit/file]
|
||||
frg.POST("/audit/file", au.AuditFile)
|
||||
}
|
||||
|
||||
frg.Use(middleware.RustAuth())
|
||||
{
|
||||
u := &api.User{}
|
||||
@@ -121,6 +131,7 @@ func WebClientRoutes(frg *gin.RouterGroup) {
|
||||
}
|
||||
{
|
||||
frg.POST("/server-config", middleware.RustAuth(), w.ServerConfig)
|
||||
frg.POST("/server-config-v2", middleware.RustAuth(), w.ServerConfigV2)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Jwt struct {
|
||||
privateKey *rsa.PrivateKey
|
||||
Key []byte
|
||||
TokenExpireDuration time.Duration
|
||||
}
|
||||
|
||||
@@ -17,31 +16,28 @@ type UserClaims struct {
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func NewJwt(privateKeyFile string, tokenExpireDuration time.Duration) *Jwt {
|
||||
privateKeyContent, err := os.ReadFile(privateKeyFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyContent)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
func NewJwt(key string, tokenExpireDuration time.Duration) *Jwt {
|
||||
return &Jwt{
|
||||
privateKey: privateKey,
|
||||
Key: []byte(key),
|
||||
TokenExpireDuration: tokenExpireDuration,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Jwt) GenerateToken(userId uint) string {
|
||||
t := jwt.NewWithClaims(jwt.SigningMethodRS256,
|
||||
if len(s.Key) == 0 {
|
||||
fmt.Println("jwt key is nil")
|
||||
return ""
|
||||
}
|
||||
t := jwt.NewWithClaims(jwt.SigningMethodHS256,
|
||||
UserClaims{
|
||||
UserId: userId,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.TokenExpireDuration)),
|
||||
},
|
||||
})
|
||||
token, err := t.SignedString(s.privateKey)
|
||||
token, err := t.SignedString(s.Key)
|
||||
if err != nil {
|
||||
fmt.Printf("jwt token generate error: %v", err)
|
||||
return ""
|
||||
}
|
||||
return token
|
||||
@@ -49,7 +45,7 @@ func (s *Jwt) GenerateToken(userId uint) string {
|
||||
|
||||
func (s *Jwt) ParseToken(tokenString string) (uint, error) {
|
||||
token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return s.privateKey.Public(), nil
|
||||
return s.Key, nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
||||
@@ -2,21 +2,34 @@ package model
|
||||
|
||||
type LoginLog struct {
|
||||
IdModel
|
||||
UserId uint `json:"user_id"`
|
||||
Client string `json:"client"` //webadmin,webclient,app,
|
||||
Uuid string `json:"uuid"`
|
||||
Ip string `json:"ip"`
|
||||
Type string `json:"type"` //account,oauth
|
||||
Platform string `json:"platform"` //windows,linux,mac,android,ios
|
||||
|
||||
UserId uint `json:"user_id" gorm:"default:0;not null;"`
|
||||
Client string `json:"client"` //webadmin,webclient,app,
|
||||
DeviceId string `json:"device_id"`
|
||||
Uuid string `json:"uuid"`
|
||||
Ip string `json:"ip"`
|
||||
Type string `json:"type"` //account,oauth
|
||||
Platform string `json:"platform"` //windows,linux,mac,android,ios
|
||||
UserTokenId uint `json:"user_token_id" gorm:"default:0;not null;"`
|
||||
IsDeleted uint `json:"is_deleted" gorm:"default:0;not null;"`
|
||||
TimeModel
|
||||
}
|
||||
|
||||
const (
|
||||
LoginLogClientWebAdmin = "webadmin"
|
||||
LoginLogClientWeb = "webclient"
|
||||
LoginLogClientApp = "app"
|
||||
)
|
||||
|
||||
const (
|
||||
LoginLogTypeAccount = "account"
|
||||
LoginLogTypeOauth = "oauth"
|
||||
)
|
||||
|
||||
const (
|
||||
IsDeletedNo = 0
|
||||
IsDeletedYes = 1
|
||||
)
|
||||
|
||||
type LoginLogList struct {
|
||||
LoginLogs []*LoginLog `json:"list"`
|
||||
Pagination
|
||||
|
||||
136
model/oauth.go
@@ -1,23 +1,145 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const OIDC_DEFAULT_SCOPES = "openid,profile,email"
|
||||
|
||||
const (
|
||||
// make sure the value shouldbe lowercase
|
||||
OauthTypeGithub string = "github"
|
||||
OauthTypeGoogle string = "google"
|
||||
OauthTypeOidc string = "oidc"
|
||||
OauthTypeWebauth string = "webauth"
|
||||
)
|
||||
|
||||
// Validate the oauth type
|
||||
func ValidateOauthType(oauthType string) error {
|
||||
switch oauthType {
|
||||
case OauthTypeGithub, OauthTypeGoogle, OauthTypeOidc, OauthTypeWebauth:
|
||||
return nil
|
||||
default:
|
||||
return errors.New("invalid Oauth type")
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
UserEndpointGithub string = "https://api.github.com/user"
|
||||
IssuerGoogle string = "https://accounts.google.com"
|
||||
)
|
||||
|
||||
type Oauth struct {
|
||||
IdModel
|
||||
Op string `json:"op"`
|
||||
OauthType string `json:"oauth_type"`
|
||||
ClientId string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RedirectUrl string `json:"redirect_url"`
|
||||
AutoRegister *bool `json:"auto_register"`
|
||||
Scopes string `json:"scopes"`
|
||||
Issuer string `json:"issuer"`
|
||||
Issuer string `json:"issuer"`
|
||||
TimeModel
|
||||
}
|
||||
|
||||
const (
|
||||
OauthTypeGithub = "github"
|
||||
OauthTypeGoogle = "google"
|
||||
OauthTypeOidc = "oidc"
|
||||
OauthTypeWebauth = "webauth"
|
||||
)
|
||||
// Helper function to format oauth info, it's used in the update and create method
|
||||
func (oa *Oauth) FormatOauthInfo() error {
|
||||
oauthType := strings.TrimSpace(oa.OauthType)
|
||||
err := ValidateOauthType(oa.OauthType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch oauthType {
|
||||
case OauthTypeGithub:
|
||||
oa.Op = OauthTypeGithub
|
||||
case OauthTypeGoogle:
|
||||
oa.Op = OauthTypeGoogle
|
||||
}
|
||||
// check if the op is empty, set the default value
|
||||
op := strings.TrimSpace(oa.Op)
|
||||
if op == "" && oauthType == OauthTypeOidc {
|
||||
oa.Op = OauthTypeOidc
|
||||
}
|
||||
// check the issuer, if the oauth type is google and the issuer is empty, set the issuer to the default value
|
||||
issuer := strings.TrimSpace(oa.Issuer)
|
||||
// If the oauth type is google and the issuer is empty, set the issuer to the default value
|
||||
if oauthType == OauthTypeGoogle && issuer == "" {
|
||||
oa.Issuer = IssuerGoogle
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type OauthUser struct {
|
||||
OpenId string `json:"open_id" gorm:"not null;index"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
VerifiedEmail bool `json:"verified_email,omitempty"`
|
||||
Picture string `json:"picture,omitempty"`
|
||||
}
|
||||
|
||||
func (ou *OauthUser) ToUser(user *User, overideUsername bool) {
|
||||
if overideUsername {
|
||||
user.Username = ou.Username
|
||||
}
|
||||
user.Email = ou.Email
|
||||
user.Nickname = ou.Name
|
||||
user.Avatar = ou.Picture
|
||||
}
|
||||
|
||||
type OauthUserBase struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
type OidcUser struct {
|
||||
OauthUserBase
|
||||
Sub string `json:"sub"`
|
||||
VerifiedEmail bool `json:"email_verified"`
|
||||
PreferredUsername string `json:"preferred_username"`
|
||||
Picture string `json:"picture"`
|
||||
}
|
||||
|
||||
func (ou *OidcUser) ToOauthUser() *OauthUser {
|
||||
var username string
|
||||
// 使用 PreferredUsername,如果不存在,降级到 Email 前缀
|
||||
if ou.PreferredUsername != "" {
|
||||
username = ou.PreferredUsername
|
||||
} else {
|
||||
username = strings.ToLower(ou.Email)
|
||||
}
|
||||
|
||||
return &OauthUser{
|
||||
OpenId: ou.Sub,
|
||||
Name: ou.Name,
|
||||
Username: username,
|
||||
Email: ou.Email,
|
||||
VerifiedEmail: ou.VerifiedEmail,
|
||||
Picture: ou.Picture,
|
||||
}
|
||||
}
|
||||
|
||||
type GithubUser struct {
|
||||
OauthUserBase
|
||||
Id int `json:"id"`
|
||||
Login string `json:"login"`
|
||||
AvatarUrl string `json:"avatar_url"`
|
||||
VerifiedEmail bool `json:"verified_email"`
|
||||
}
|
||||
|
||||
func (gu *GithubUser) ToOauthUser() *OauthUser {
|
||||
username := strings.ToLower(gu.Login)
|
||||
return &OauthUser{
|
||||
OpenId: strconv.Itoa(gu.Id),
|
||||
Name: gu.Name,
|
||||
Username: username,
|
||||
Email: gu.Email,
|
||||
VerifiedEmail: gu.VerifiedEmail,
|
||||
Picture: gu.AvatarUrl,
|
||||
}
|
||||
}
|
||||
|
||||
type OauthList struct {
|
||||
Oauths []*Oauth `json:"list"`
|
||||
|
||||