Compare commits
No commits in common. "8b4dc49cf18e17021777a601bc39bb446b1926e6" and "2dee05c7f3673e939157eefd8568fdfae087f702" have entirely different histories.
8b4dc49cf1
...
2dee05c7f3
|
@ -0,0 +1,2 @@
|
||||||
|
*.go linguist-detectable=true
|
||||||
|
*.js linguist-detectable=false
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Always validate the PR title AND all the commits
|
||||||
|
titleAndCommits: true
|
||||||
|
# Require at least one commit to be valid
|
||||||
|
# this is only relevant when using commitsOnly: true or titleAndCommits: true,
|
||||||
|
# which validate all commits by default
|
||||||
|
anyCommit: true
|
||||||
|
# Allow use of Merge commits (eg on github: "Merge branch 'master' into feature/ride-unicorns")
|
||||||
|
# this is only relevant when using commitsOnly: true (or titleAndCommits: true)
|
||||||
|
allowMergeCommits: false
|
||||||
|
# Allow use of Revert commits (eg on github: "Revert "feat: ride unicorns"")
|
||||||
|
# this is only relevant when using commitsOnly: true (or titleAndCommits: true)
|
||||||
|
allowRevertCommits: false
|
|
@ -0,0 +1,109 @@
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
name: Front-end
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 16
|
||||||
|
- uses: c-hive/gha-yarn-cache@v2
|
||||||
|
with:
|
||||||
|
directory: ./web
|
||||||
|
- run: yarn install && CI=false yarn run build
|
||||||
|
working-directory: ./web
|
||||||
|
|
||||||
|
backend:
|
||||||
|
name: Back-end
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: '^1.16.5'
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
go build -race -ldflags "-extldflags '-static'"
|
||||||
|
working-directory: ./
|
||||||
|
|
||||||
|
linter:
|
||||||
|
name: Go-Linter
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: '^1.16.5'
|
||||||
|
|
||||||
|
- name: golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v3
|
||||||
|
with:
|
||||||
|
version: v1.29
|
||||||
|
args: --disable-all -E gofumpt --max-same-issues=0 --max-issues-per-linter=0 --timeout 5m ./...
|
||||||
|
|
||||||
|
release-and-push:
|
||||||
|
name: Release And Push
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository == 'casbin/casnode' && github.event_name == 'push'
|
||||||
|
needs: [ frontend, backend, linter ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 16
|
||||||
|
|
||||||
|
- name: Fetch Previous version
|
||||||
|
id: get-previous-tag
|
||||||
|
uses: actions-ecosystem/action-get-latest-tag@v1.6.0
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
run: yarn global add semantic-release@17.4.4 && semantic-release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
|
||||||
|
|
||||||
|
- name: Fetch Current version
|
||||||
|
id: get-current-tag
|
||||||
|
uses: actions-ecosystem/action-get-latest-tag@v1.6.0
|
||||||
|
|
||||||
|
- name: Decide Should_Push Or Not
|
||||||
|
id: should_push
|
||||||
|
run: |
|
||||||
|
old_version=${{steps.get-previous-tag.outputs.tag}}
|
||||||
|
new_version=${{steps.get-current-tag.outputs.tag }}
|
||||||
|
|
||||||
|
old_array=(${old_version//\./ })
|
||||||
|
new_array=(${new_version//\./ })
|
||||||
|
|
||||||
|
if [ ${old_array[0]} != ${new_array[0]} ]
|
||||||
|
then
|
||||||
|
echo "push='true'" >> GITHUB_OUTPUT
|
||||||
|
elif [ ${old_array[1]} != ${new_array[1]} ]
|
||||||
|
then
|
||||||
|
echo "push='true'" >> GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "push='false'" >> GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
if: github.repository == 'casbin/casnode' && github.event_name == 'push' &&steps.should_push.outputs.push=='true'
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Push to Docker Hub
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
if: github.repository == 'casbin/casnode' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
tags: casbin/casnode:${{steps.get-current-tag.outputs.tag }},casbin/casnode:latest
|
|
@ -0,0 +1,35 @@
|
||||||
|
name: Crowdin Action
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
synchronize-with-crowdin:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: crowdin action
|
||||||
|
uses: crowdin/github-action@1.4.8
|
||||||
|
with:
|
||||||
|
upload_translations: true
|
||||||
|
|
||||||
|
download_translations: true
|
||||||
|
push_translations: true
|
||||||
|
commit_message: 'refactor: New Crowdin translations by Github Action'
|
||||||
|
|
||||||
|
localization_branch_name: l10n_crowdin_action
|
||||||
|
create_pull_request: true
|
||||||
|
pull_request_title: 'refactor: New Crowdin translations'
|
||||||
|
|
||||||
|
crowdin_branch_name: l10n_branch
|
||||||
|
config: './web/crowdin.yml'
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CROWDIN_PROJECT_ID: '479711'
|
||||||
|
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
|
@ -1,7 +1,3 @@
|
||||||
# ---> Go
|
|
||||||
# If you prefer the allow list template instead of the deny list, see community template:
|
|
||||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
|
||||||
#
|
|
||||||
# Binaries for programs and plugins
|
# Binaries for programs and plugins
|
||||||
*.exe
|
*.exe
|
||||||
*.exe~
|
*.exe~
|
||||||
|
@ -18,6 +14,12 @@
|
||||||
# Dependency directories (remove the comment below to include it)
|
# Dependency directories (remove the comment below to include it)
|
||||||
# vendor/
|
# vendor/
|
||||||
|
|
||||||
# Go workspace file
|
.idea/
|
||||||
go.work
|
*.iml
|
||||||
|
|
||||||
|
tmp/
|
||||||
|
tmpFiles/
|
||||||
|
*.tmp
|
||||||
|
logs/
|
||||||
|
lastupdate.tmp
|
||||||
|
commentsRouter*.go
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"debug": true,
|
||||||
|
"branches": [
|
||||||
|
"+([0-9])?(.{+([0-9]),x}).x",
|
||||||
|
"master",
|
||||||
|
{
|
||||||
|
"name": "rc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "beta",
|
||||||
|
"prerelease": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "alpha",
|
||||||
|
"prerelease": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"@semantic-release/commit-analyzer",
|
||||||
|
"@semantic-release/release-notes-generator",
|
||||||
|
"@semantic-release/github"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
FROM golang:1.17 AS BACK
|
||||||
|
WORKDIR /go/src/casnode
|
||||||
|
COPY . .
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOPROXY=https://goproxy.cn,direct go build -ldflags="-w -s" -o server . \
|
||||||
|
&& apt update && apt install wait-for-it && chmod +x /usr/bin/wait-for-it
|
||||||
|
|
||||||
|
FROM node:14.17.6 AS FRONT
|
||||||
|
WORKDIR /web
|
||||||
|
COPY ./web .
|
||||||
|
RUN yarn config set registry https://registry.npmmirror.com
|
||||||
|
RUN yarn install && yarn run build
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
RUN sed -i 's/https/http/' /etc/apk/repositories
|
||||||
|
RUN apk add curl
|
||||||
|
LABEL MAINTAINER="https://casnode.org/"
|
||||||
|
|
||||||
|
COPY --from=BACK /go/src/casnode/ ./
|
||||||
|
COPY --from=BACK /usr/bin/wait-for-it ./
|
||||||
|
RUN mkdir -p web/build && apk add --no-cache bash coreutils
|
||||||
|
COPY --from=FRONT /web/build /web/build
|
||||||
|
CMD ./wait-for-it db:3306 -- ./server
|
210
LICENSE
210
LICENSE
|
@ -1,73 +1,201 @@
|
||||||
Apache License
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
http://www.apache.org/licenses/
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
1. Definitions.
|
1. Definitions.
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
112
README.md
112
README.md
|
@ -1,2 +1,110 @@
|
||||||
# caswaf
|
<h1 align="center" style="border-bottom: none;">📦⚡️ Casnode</h1>
|
||||||
|
<h3 align="center">An open-source forum (BBS) software developed by Go and React.</h3>
|
||||||
|
<p align="center">
|
||||||
|
<a href="#badge">
|
||||||
|
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
|
||||||
|
</a>
|
||||||
|
<a href="https://hub.docker.com/r/casbin/casnode">
|
||||||
|
<img alt="docker pull casbin/casnode" src="https://img.shields.io/docker/pulls/casbin/casnode.svg">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/casbin/casnode/actions/workflows/build.yml">
|
||||||
|
<img alt="GitHub Workflow Status (branch)" src="https://github.com/casbin/jcasbin/workflows/build/badge.svg?style=flat-square">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/casbin/casnode/releases/latest">
|
||||||
|
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casbin/casnode.svg">
|
||||||
|
</a>
|
||||||
|
<a href="https://hub.docker.com/repository/docker/casbin/casnode">
|
||||||
|
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://goreportcard.com/report/github.com/casbin/casnode">
|
||||||
|
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casbin/casnode?style=flat-square">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/casbin/casnode/blob/master/LICENSE">
|
||||||
|
<img src="https://img.shields.io/github/license/casbin/casnode?style=flat-square" alt="license">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/casbin/casnode/issues">
|
||||||
|
<img alt="GitHub issues" src="https://img.shields.io/github/issues/casbin/casnode?style=flat-square">
|
||||||
|
</a>
|
||||||
|
<a href="#">
|
||||||
|
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casbin/casnode?style=flat-square">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/casbin/casnode/network">
|
||||||
|
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casbin/casnode?style=flat-square">
|
||||||
|
</a>
|
||||||
|
<a href="https://crowdin.com/project/casnode">
|
||||||
|
<img alt="Crowdin" src="https://badges.crowdin.net/casnode/localized.svg">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Online demo
|
||||||
|
|
||||||
|
Deployed site: https://forum.casbin.com/
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Casnode contains 2 parts:
|
||||||
|
|
||||||
|
Name | Description | Language | Source code
|
||||||
|
----|------|----|----
|
||||||
|
Frontend | Web frontend UI for Casnode | Javascript + React | https://github.com/casbin/casnode/tree/master/web
|
||||||
|
Backend | RESTful API backend for Casnode | Golang + Beego + MySQL | https://github.com/casbin/casnode
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
Casnode uses Casdoor to manage members. So you need to create an organization and an application for Casnode in a Casdoor instance.
|
||||||
|
### Necessary configuration
|
||||||
|
|
||||||
|
#### Get the code
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go get github.com/casbin/casnode
|
||||||
|
go get github.com/casdoor/casdoor
|
||||||
|
```
|
||||||
|
or
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/casbin/casnode
|
||||||
|
git clone https://github.com/casdoor/casdoor
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Setup database
|
||||||
|
|
||||||
|
Casnode will store its users, nodes and topics informations in a MySQL database named: `casnode`, will create it if not existed. The DB connection string can be specified at: https://github.com/casbin/casnode/blob/master/conf/app.conf
|
||||||
|
|
||||||
|
```ini
|
||||||
|
dataSourceName = root:123@tcp(localhost:3306)/
|
||||||
|
```
|
||||||
|
|
||||||
|
Casnode uses XORM to connect to DB, so all DBs supported by XORM can also be used.
|
||||||
|
|
||||||
|
#### Run casnode
|
||||||
|
- Configure and run casnode by yourself. If you want to learn more about casnode, you see [casnode installation](https://casnode.org/docs/installation).
|
||||||
|
- Install casnode using docker. you see [installation by docker](https://casnode.org/docs/Docker).
|
||||||
|
- Install casnode using BTpanel. you see [installation by BTpanel](https://casnode.org/docs/BTpanel).
|
||||||
|
- Open browser:
|
||||||
|
|
||||||
|
http://localhost:3000/
|
||||||
|
|
||||||
|
### Optional configuration
|
||||||
|
|
||||||
|
#### Setup your forum to enable some third-party login platform
|
||||||
|
|
||||||
|
Casnode uses Casdoor to manage members. If you want to log in with oauth, you should see [casdoor oauth configuration](https://casdoor.org/docs/provider/OAuth).
|
||||||
|
|
||||||
|
#### OSS, Mail, and SMS services
|
||||||
|
|
||||||
|
Casnode uses Casdoor to upload files to cloud storage, send Emails and send SMSs. See Casdoor for more details.
|
||||||
|
|
||||||
|
#### Github corner
|
||||||
|
|
||||||
|
We added a Github icon in the upper right corner, linking to your Github repository address.
|
||||||
|
You could set `ShowGithubCorner` to hidden it.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export const ShowGithubCorner = true
|
||||||
|
|
||||||
|
export const GithubRepo = "https://github.com/casbin/casnode" //your github repository
|
||||||
|
```
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
var adapter *object.Adapter
|
||||||
|
|
||||||
|
func TestTopicTag(t *testing.T) {
|
||||||
|
topics := []*object.Topic{}
|
||||||
|
adapter = object.NewAdapter(beego.AppConfig.String("driverName"), beego.AppConfig.String("dataSourceName"), beego.AppConfig.String("dbName"))
|
||||||
|
err := adapter.Engine.Table("topic").Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, topic := range topics {
|
||||||
|
if len(topic.Tags) == 0 || topic.Tags == nil {
|
||||||
|
topic.Tags = service.Finalword(topic.Content)
|
||||||
|
_, err := adapter.Engine.Id(topic.Id).Update(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package casdoor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
adapter *Adapter = nil
|
||||||
|
CasdoorOrganization string
|
||||||
|
)
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
SessionKey string `xorm:"char(64) notnull pk"`
|
||||||
|
SessionData []uint8 `xorm:"blob"`
|
||||||
|
SessionExpiry int `xorm:"notnull"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitCasdoorAdapter() {
|
||||||
|
casdoorDbName := beego.AppConfig.String("casdoorDbName")
|
||||||
|
if casdoorDbName == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter = NewAdapter(beego.AppConfig.String("driverName"), beego.AppConfig.String("dataSourceName"), beego.AppConfig.String("casdoorDbName"))
|
||||||
|
|
||||||
|
CasdoorOrganization = beego.AppConfig.String("casdoorOrganization")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapter represents the MySQL adapter for policy storage.
|
||||||
|
type Adapter struct {
|
||||||
|
driverName string
|
||||||
|
dataSourceName string
|
||||||
|
dbName string
|
||||||
|
Engine *xorm.Engine
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalizer is the destructor for Adapter.
|
||||||
|
func finalizer(a *Adapter) {
|
||||||
|
err := a.Engine.Close()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAdapter is the constructor for Adapter.
|
||||||
|
func NewAdapter(driverName string, dataSourceName string, dbName string) *Adapter {
|
||||||
|
a := &Adapter{}
|
||||||
|
a.driverName = driverName
|
||||||
|
a.dataSourceName = dataSourceName
|
||||||
|
a.dbName = dbName
|
||||||
|
|
||||||
|
// Open the DB, create it if not existed.
|
||||||
|
a.open()
|
||||||
|
|
||||||
|
// Call the destructor when the object is released.
|
||||||
|
runtime.SetFinalizer(a, finalizer)
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) open() {
|
||||||
|
Engine, err := xorm.NewEngine(a.driverName, a.dataSourceName+a.dbName)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Engine = Engine
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) close() {
|
||||||
|
a.Engine.Close()
|
||||||
|
a.Engine = nil
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package casdoor
|
||||||
|
|
||||||
|
import "github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
|
||||||
|
func GetUsers() []*casdoorsdk.User {
|
||||||
|
if adapter != nil {
|
||||||
|
return getUsers()
|
||||||
|
} else {
|
||||||
|
users, err := casdoorsdk.GetUsers()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSortedUsers(sorter string, limit int) []*casdoorsdk.User {
|
||||||
|
if adapter != nil {
|
||||||
|
return getSortedUsers(sorter, limit)
|
||||||
|
} else {
|
||||||
|
users, err := casdoorsdk.GetSortedUsers(sorter, limit)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserCount() int {
|
||||||
|
if adapter != nil {
|
||||||
|
return getUserCount()
|
||||||
|
} else {
|
||||||
|
count, err := casdoorsdk.GetUserCount("")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetOnlineUserCount() int {
|
||||||
|
if adapter != nil {
|
||||||
|
return getOnlineUserCount()
|
||||||
|
} else {
|
||||||
|
count, err := casdoorsdk.GetUserCount("1")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserByEmail(email string) *casdoorsdk.User {
|
||||||
|
if adapter != nil {
|
||||||
|
return getUserByEmail(email)
|
||||||
|
} else {
|
||||||
|
user, err := casdoorsdk.GetUserByEmail(email)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,220 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package casdoor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
"xorm.io/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getUsers() []*casdoorsdk.User {
|
||||||
|
owner := CasdoorOrganization
|
||||||
|
|
||||||
|
if adapter == nil {
|
||||||
|
panic("casdoor adapter is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
users := []*casdoorsdk.User{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Find(&users, &casdoorsdk.User{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSortedUsers(sorter string, limit int) []*casdoorsdk.User {
|
||||||
|
owner := CasdoorOrganization
|
||||||
|
|
||||||
|
if adapter == nil {
|
||||||
|
panic("casdoor adapter is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
users := []*casdoorsdk.User{}
|
||||||
|
err := adapter.Engine.Desc(sorter).Limit(limit, 0).Find(&users, &casdoorsdk.User{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserCount() int {
|
||||||
|
owner := CasdoorOrganization
|
||||||
|
|
||||||
|
if adapter == nil {
|
||||||
|
panic("casdoor adapter is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := adapter.Engine.Count(&casdoorsdk.User{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOnlineUserCount() int {
|
||||||
|
owner := CasdoorOrganization
|
||||||
|
|
||||||
|
if adapter == nil {
|
||||||
|
panic("casdoor adapter is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := adapter.Engine.Where("is_online = ?", 1).Count(&casdoorsdk.User{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUser(name string) *casdoorsdk.User {
|
||||||
|
owner := CasdoorOrganization
|
||||||
|
|
||||||
|
if adapter == nil {
|
||||||
|
panic("casdoor adapter is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if owner == "" || name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
user := casdoorsdk.User{Owner: owner, Name: name}
|
||||||
|
existed, err := adapter.Engine.Get(&user)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &user
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserByEmail(email string) *casdoorsdk.User {
|
||||||
|
owner := CasdoorOrganization
|
||||||
|
|
||||||
|
if adapter == nil {
|
||||||
|
panic("casdoor adapter is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if owner == "" || email == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
user := casdoorsdk.User{Owner: owner, Email: email}
|
||||||
|
existed, err := adapter.Engine.Get(&user)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &user
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddUser(user *casdoorsdk.User) bool {
|
||||||
|
if adapter == nil {
|
||||||
|
panic("casdoor adapter is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.Insert(user)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddUsers(users []*casdoorsdk.User) bool {
|
||||||
|
if adapter == nil {
|
||||||
|
panic("casdoor adapter is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(users) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.Insert(users)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddUsersInBatch(users []*casdoorsdk.User) bool {
|
||||||
|
batchSize := 1000
|
||||||
|
|
||||||
|
if len(users) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected := false
|
||||||
|
for i := 0; i < (len(users)-1)/batchSize+1; i++ {
|
||||||
|
start := i * batchSize
|
||||||
|
end := (i + 1) * batchSize
|
||||||
|
if end > len(users) {
|
||||||
|
end = len(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := users[start:end]
|
||||||
|
fmt.Printf("Add users: [%d - %d].\n", start, end)
|
||||||
|
if AddUsers(tmp) {
|
||||||
|
affected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUser(owner string, name string, user *casdoorsdk.User) (bool, error) {
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(user)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateUser(owner string, name string, user *casdoorsdk.User) bool {
|
||||||
|
if adapter == nil {
|
||||||
|
panic("casdoor adapter is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
var affected bool
|
||||||
|
var err error
|
||||||
|
times := 0
|
||||||
|
for {
|
||||||
|
affected, err = updateUser(owner, name, user)
|
||||||
|
if err != nil {
|
||||||
|
times += 1
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
if times >= 10 {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return affected
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
appname = casnode
|
||||||
|
httpport = 7000
|
||||||
|
runmode = dev
|
||||||
|
SessionOn = true
|
||||||
|
copyrequestbody = true
|
||||||
|
redisEndpoint =
|
||||||
|
driverName = mysql
|
||||||
|
dataSourceName = root:123@tcp(localhost:3306)/
|
||||||
|
dbName = casnode
|
||||||
|
domain = "forum.casbin.com"
|
||||||
|
casdoorDbName = casdoor
|
||||||
|
casdoorOrganization = "casbin"
|
||||||
|
casdoorApplication = "app-casnode"
|
||||||
|
casdoorStorageEndpoint = "https://cdn.casbin.com/"
|
||||||
|
casdoorEndpoint = http://localhost:8000
|
||||||
|
clientId = 014ae4bd048734ca2dea
|
||||||
|
clientSecret = xxx
|
||||||
|
httpProxy = "127.0.0.1:10808"
|
||||||
|
initScore = 2000
|
||||||
|
enableNestedReply = true
|
||||||
|
cacheExpireSeconds = 60
|
||||||
|
chromeCtxNum = 1
|
|
@ -0,0 +1,124 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed token_jwt_key.pem
|
||||||
|
var JwtPublicKey string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
InitAuthConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitAuthConfig() {
|
||||||
|
casdoorEndpoint := strings.TrimRight(beego.AppConfig.String("casdoorEndpoint"), "/")
|
||||||
|
clientId := beego.AppConfig.String("clientId")
|
||||||
|
clientSecret := beego.AppConfig.String("clientSecret")
|
||||||
|
casdoorOrganization := beego.AppConfig.String("casdoorOrganization")
|
||||||
|
casdoorApplication := beego.AppConfig.String("casdoorApplication")
|
||||||
|
|
||||||
|
casdoorsdk.InitConfig(casdoorEndpoint, clientId, clientSecret, JwtPublicKey, casdoorOrganization, casdoorApplication)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title Signin
|
||||||
|
// @Description sign in as a member
|
||||||
|
// @Param code QueryString string true "The code to sign in"
|
||||||
|
// @Param state QueryString string true "The state"
|
||||||
|
// @Success 200 {object} controllers.api_controller.Response The Response object
|
||||||
|
// @router /signin [post]
|
||||||
|
// @Tag Account API
|
||||||
|
func (c *ApiController) Signin() {
|
||||||
|
code := c.Input().Get("code")
|
||||||
|
state := c.Input().Get("state")
|
||||||
|
|
||||||
|
token, err := casdoorsdk.GetOAuthToken(code, state)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := casdoorsdk.ParseJwtToken(token.AccessToken)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := object.UpdateMemberOnlineStatus(&claims.User, true, util.GetCurrentTime())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims.AccessToken = token.AccessToken
|
||||||
|
c.SetSessionClaims(claims)
|
||||||
|
|
||||||
|
c.ResponseOk(claims, affected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title Signout
|
||||||
|
// @Description sign out the current member
|
||||||
|
// @Success 200 {object} controllers.api_controller.Response The Response object
|
||||||
|
// @router /signout [post]
|
||||||
|
// @Tag Account API
|
||||||
|
func (c *ApiController) Signout() {
|
||||||
|
claims := c.GetSessionClaims()
|
||||||
|
if claims != nil {
|
||||||
|
_, err := object.UpdateMemberOnlineStatus(&claims.User, false, util.GetCurrentTime())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SetSessionClaims(nil)
|
||||||
|
|
||||||
|
c.ResponseOk()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetAccount
|
||||||
|
// @Description Get current account
|
||||||
|
// @Success 200 {object} controllers.api_controller.Response The Response object
|
||||||
|
// @router /get-account [get]
|
||||||
|
// @Tag Account API
|
||||||
|
func (c *ApiController) GetAccount() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := c.GetSessionClaims()
|
||||||
|
|
||||||
|
c.ResponseOk(claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) UpdateAccountBalance(amount int) {
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
user.Score += amount
|
||||||
|
c.SetSessionUser(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) UpdateAccountConsumptionSum(amount int) {
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
user.Karma += amount
|
||||||
|
c.SetSessionUser(user)
|
||||||
|
}
|
|
@ -0,0 +1,205 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Tag Balance API
|
||||||
|
// @Title AddThanks
|
||||||
|
// @router /add-thanks [post]
|
||||||
|
func (c *ApiController) AddThanks() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
id := util.ParseInt(c.Input().Get("id"))
|
||||||
|
thanksType := c.Input().Get("thanksType") // 1 means topic, 2 means reply
|
||||||
|
|
||||||
|
var author *casdoorsdk.User
|
||||||
|
if thanksType == "2" {
|
||||||
|
author = object.GetReplyAuthor(id)
|
||||||
|
} else {
|
||||||
|
author = object.GetTopicAuthor(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
consumerRecord := object.ConsumptionRecord{
|
||||||
|
ConsumerId: author.Name,
|
||||||
|
ReceiverId: GetUserName(user),
|
||||||
|
ObjectId: id,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
}
|
||||||
|
|
||||||
|
receiverRecord := object.ConsumptionRecord{
|
||||||
|
ConsumerId: GetUserName(user),
|
||||||
|
ReceiverId: author.Name,
|
||||||
|
ObjectId: id,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if thanksType == "2" || thanksType == "1" {
|
||||||
|
if thanksType == "2" {
|
||||||
|
consumerRecord.Amount = -object.ReplyThanksCost
|
||||||
|
receiverRecord.Amount = object.ReplyThanksCost
|
||||||
|
consumerRecord.ConsumptionType = 5
|
||||||
|
receiverRecord.ConsumptionType = 3
|
||||||
|
} else {
|
||||||
|
consumerRecord.Amount = -object.TopicThanksCost
|
||||||
|
receiverRecord.Amount = object.TopicThanksCost
|
||||||
|
consumerRecord.ConsumptionType = 4
|
||||||
|
receiverRecord.ConsumptionType = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
consumerRecord.Balance = object.GetMemberBalance(user) + consumerRecord.Amount
|
||||||
|
if consumerRecord.Balance < 0 {
|
||||||
|
c.ResponseError("You don't have enough balance.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
receiverRecord.Balance = object.GetMemberBalance(user) + receiverRecord.Amount
|
||||||
|
object.AddBalance(&receiverRecord)
|
||||||
|
object.AddBalance(&consumerRecord)
|
||||||
|
|
||||||
|
_, err := object.UpdateMemberBalance(user, consumerRecord.Amount)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = object.UpdateMemberBalance(user, receiverRecord.Amount)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if thanksType == "2" {
|
||||||
|
object.AddReplyThanksNum(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.UpdateAccountBalance(consumerRecord.Amount)
|
||||||
|
|
||||||
|
_, err = object.UpdateMemberConsumptionSum(user, -consumerRecord.Amount)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.UpdateAccountConsumptionSum(-consumerRecord.Amount)
|
||||||
|
|
||||||
|
c.ResponseOk()
|
||||||
|
} else {
|
||||||
|
c.ResponseError(fmt.Sprintf("wrong thanksType: %s", thanksType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tag Balance API
|
||||||
|
// @Title GetConsumptionRecord
|
||||||
|
// @router /get-consumption-record [get]
|
||||||
|
func (c *ApiController) GetConsumptionRecord() {
|
||||||
|
username := c.GetSessionUsername()
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
pageStr := c.Input().Get("page")
|
||||||
|
defaultLimit := object.DefaultBalancePageNum
|
||||||
|
|
||||||
|
var limit, offset int
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit = util.ParseInt(limitStr)
|
||||||
|
} else {
|
||||||
|
limit = defaultLimit
|
||||||
|
}
|
||||||
|
if len(pageStr) != 0 {
|
||||||
|
page := util.ParseInt(pageStr)
|
||||||
|
offset = page*limit - limit
|
||||||
|
}
|
||||||
|
|
||||||
|
res := object.GetMemberConsumptionRecord(username, limit, offset)
|
||||||
|
num := object.GetMemberConsumptionRecordNum(username)
|
||||||
|
|
||||||
|
c.ResponseOk(res, num)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tag Balance API
|
||||||
|
// @Title GetCheckinBonus
|
||||||
|
// @router /get-checkin-bonus [get]
|
||||||
|
func (c *ApiController) GetCheckinBonus() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
checkinDate := object.GetMemberCheckinDate(user)
|
||||||
|
date := util.GetDateStr()
|
||||||
|
if date == checkinDate {
|
||||||
|
c.ResponseError("You have received the daily checkin bonus today.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
maxBonus := object.MaxDailyCheckinBonus
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
bonus := rand.Intn(maxBonus)
|
||||||
|
|
||||||
|
record := object.ConsumptionRecord{
|
||||||
|
// Id: util.IntToString(object.GetConsumptionRecordId() + 1),
|
||||||
|
Amount: bonus,
|
||||||
|
Balance: object.GetMemberBalance(user) + bonus,
|
||||||
|
ReceiverId: GetUserName(user),
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
ConsumptionType: 1,
|
||||||
|
}
|
||||||
|
object.AddBalance(&record)
|
||||||
|
|
||||||
|
_, err := object.UpdateMemberBalance(user, bonus)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = object.UpdateMemberCheckinDate(user, date)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.UpdateAccountBalance(record.Amount)
|
||||||
|
|
||||||
|
c.ResponseOk(bonus)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /get-checkin-bonus-status [get]
|
||||||
|
// @Tag Balance API
|
||||||
|
// @Title GetCheckinBonusStatus
|
||||||
|
func (c *ApiController) GetCheckinBonusStatus() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
checkinDate := object.GetMemberCheckinDate(user)
|
||||||
|
date := util.GetDateStr()
|
||||||
|
|
||||||
|
res := checkinDate == date
|
||||||
|
|
||||||
|
c.ResponseOk(res, date)
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ApiController struct {
|
||||||
|
beego.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gob.Register(casdoorsdk.Claims{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserName(user *casdoorsdk.User) string {
|
||||||
|
if user == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetSessionClaims() *casdoorsdk.Claims {
|
||||||
|
s := c.GetSession("user")
|
||||||
|
if s == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := s.(casdoorsdk.Claims)
|
||||||
|
return &claims
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) SetSessionClaims(claims *casdoorsdk.Claims) {
|
||||||
|
if claims == nil {
|
||||||
|
c.DelSession("user")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SetSession("user", *claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetSessionUser() *casdoorsdk.User {
|
||||||
|
claims := c.GetSessionClaims()
|
||||||
|
if claims == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &claims.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) SetSessionUser(user *casdoorsdk.User) {
|
||||||
|
if user == nil {
|
||||||
|
// c.DelSession("user")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := c.GetSessionClaims()
|
||||||
|
if claims != nil {
|
||||||
|
claims.User = *user
|
||||||
|
c.SetSessionClaims(claims)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetSessionUsername() string {
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
if user == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetUserName(user)
|
||||||
|
}
|
|
@ -0,0 +1,247 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @router /add-favorites [post]
|
||||||
|
// @Tag Favorite API
|
||||||
|
// @Title AddFavorites
|
||||||
|
func (c *ApiController) AddFavorites() {
|
||||||
|
objectId := c.Input().Get("id")
|
||||||
|
favoritesType := c.Input().Get("type")
|
||||||
|
memberId := c.GetSessionUsername()
|
||||||
|
var resp Response
|
||||||
|
|
||||||
|
if object.IsFavoritesExist(favoritesType) == false {
|
||||||
|
c.ResponseError("Invalid favorites type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
favoriteStatus := object.GetFavoritesStatus(memberId, objectId, favoritesType)
|
||||||
|
if favoriteStatus {
|
||||||
|
c.ResponseOk(resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username := c.GetSessionUsername()
|
||||||
|
favorites := object.Favorites{
|
||||||
|
// Id: util.IntToString(object.GetFavoritesCount()) + username,
|
||||||
|
FavoritesType: favoritesType,
|
||||||
|
ObjectId: objectId,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
MemberId: username,
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
res := true
|
||||||
|
if favorites.FavoritesType == object.FavorTopic {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
topicId := util.ParseInt(favorites.ObjectId)
|
||||||
|
res = object.ChangeTopicFavoriteCount(topicId, 1)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if favorites.FavoritesType == object.SubscribeTopic {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
topicId := util.ParseInt(favorites.ObjectId)
|
||||||
|
res = object.ChangeTopicSubscribeCount(topicId, 1)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
res = object.AddFavorites(&favorites)
|
||||||
|
if favoritesType == object.FavorTopic {
|
||||||
|
topicId := util.ParseInt(objectId)
|
||||||
|
notification := object.Notification{
|
||||||
|
// Id: util.IntToString(object.GetNotificationId()),
|
||||||
|
NotificationType: 4,
|
||||||
|
ObjectId: topicId,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
SenderId: c.GetSessionUsername(),
|
||||||
|
ReceiverId: object.GetTopicAuthor(topicId).Name,
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
if notification.ReceiverId != notification.SenderId {
|
||||||
|
_ = object.AddNotification(¬ification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: res}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if !res {
|
||||||
|
c.ResponseError("add favorite wrong")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /delete-favorites [post]
|
||||||
|
// @Tag Favorite API
|
||||||
|
// @Title DeleteFavorites
|
||||||
|
func (c *ApiController) DeleteFavorites() {
|
||||||
|
memberId := c.GetSessionUsername()
|
||||||
|
objectId := c.Input().Get("id")
|
||||||
|
favoritesType := c.Input().Get("type")
|
||||||
|
var resp Response
|
||||||
|
|
||||||
|
if object.IsFavoritesExist(favoritesType) == false {
|
||||||
|
resp = Response{Status: "fail", Msg: "param wrong"}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
res := true
|
||||||
|
if favoritesType == object.FavorTopic {
|
||||||
|
topicId := util.ParseInt(objectId)
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
res = object.ChangeTopicFavoriteCount(topicId, -1)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if favoritesType == object.SubscribeTopic {
|
||||||
|
topicId := util.ParseInt(objectId)
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
res = object.ChangeTopicSubscribeCount(topicId, -1)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
res = object.DeleteFavorites(memberId, objectId, favoritesType)
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: res}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if !res {
|
||||||
|
resp = Response{Status: "fail", Msg: "delete favorite wrong"}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /get-favorites-status [get]
|
||||||
|
// @Tag Favorite API
|
||||||
|
// @Title GetFavoritesStatus
|
||||||
|
func (c *ApiController) GetFavoritesStatus() {
|
||||||
|
memberId := c.GetSessionUsername()
|
||||||
|
objectId := c.Input().Get("id")
|
||||||
|
favoritesType := c.Input().Get("type")
|
||||||
|
|
||||||
|
var resp Response
|
||||||
|
if object.IsFavoritesExist(favoritesType) {
|
||||||
|
res := object.GetFavoritesStatus(memberId, objectId, favoritesType)
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: res}
|
||||||
|
} else {
|
||||||
|
resp = Response{Status: "fail", Msg: "param wrong"}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /get-favorites [get]
|
||||||
|
// @Tag Favorite API
|
||||||
|
// @Title GetFavorites
|
||||||
|
func (c *ApiController) GetFavorites() {
|
||||||
|
memberId := c.GetSessionUsername()
|
||||||
|
favoritesType := c.Input().Get("type")
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
pageStr := c.Input().Get("page")
|
||||||
|
defaultLimit := object.DefaultPageNum
|
||||||
|
|
||||||
|
var limit, offset int
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit = util.ParseInt(limitStr)
|
||||||
|
} else {
|
||||||
|
limit = defaultLimit
|
||||||
|
}
|
||||||
|
if len(pageStr) != 0 {
|
||||||
|
page := util.ParseInt(pageStr)
|
||||||
|
offset = page*limit - limit
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp Response
|
||||||
|
switch favoritesType {
|
||||||
|
case object.FavorTopic:
|
||||||
|
res := object.GetTopicsFromFavorites(memberId, limit, offset, object.FavorTopic)
|
||||||
|
num := object.GetFavoritesNum(object.FavorTopic, memberId)
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: res, Data2: num}
|
||||||
|
break
|
||||||
|
case object.FollowUser:
|
||||||
|
res := object.GetFollowingNewAction(memberId, limit, offset)
|
||||||
|
num := object.GetFavoritesNum(object.FollowUser, memberId)
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: res, Data2: num}
|
||||||
|
break
|
||||||
|
case object.FavorNode:
|
||||||
|
res := object.GetNodesFromFavorites(memberId, limit, offset)
|
||||||
|
num := object.GetFavoritesNum(object.FavorNode, memberId)
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: res, Data2: num}
|
||||||
|
break
|
||||||
|
case object.SubscribeTopic:
|
||||||
|
res := object.GetTopicsFromFavorites(memberId, limit, offset, object.SubscribeTopic)
|
||||||
|
num := object.GetFavoritesNum(object.SubscribeTopic, memberId)
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: res, Data2: num}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
resp = Response{Status: "fail", Msg: "param wrong"}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /get-account-favorite-num [get]
|
||||||
|
// @Tag Favorite API
|
||||||
|
// @Title GetAccountFavoriteNum
|
||||||
|
func (c *ApiController) GetAccountFavoriteNum() {
|
||||||
|
memberId := c.GetSessionUsername()
|
||||||
|
|
||||||
|
var res [6]int
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// favorite type set,5 object.favorTopic...
|
||||||
|
typeSet := []string{object.FavorTopic, object.FollowUser, object.FavorNode, object.SubscribeTopic}
|
||||||
|
|
||||||
|
for i := 1; i <= len(typeSet); i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
i := i
|
||||||
|
go func() {
|
||||||
|
if i == 2 {
|
||||||
|
res[i] = object.GetFollowingNum(memberId)
|
||||||
|
} else {
|
||||||
|
res[i] = object.GetFavoritesNum(typeSet[i-1], memberId)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
|
@ -0,0 +1,240 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/service"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NewUploadFile struct {
|
||||||
|
FileName string `json:"fileName"`
|
||||||
|
FilePath string `json:"filePath"`
|
||||||
|
FileUrl string `json:"fileUrl"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetFiles() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
pageStr := c.Input().Get("page")
|
||||||
|
defaultLimit := object.DefaultFilePageNum
|
||||||
|
|
||||||
|
var limit, offset int
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit = util.ParseInt(limitStr)
|
||||||
|
} else {
|
||||||
|
limit = defaultLimit
|
||||||
|
}
|
||||||
|
if len(pageStr) != 0 {
|
||||||
|
page := util.ParseInt(pageStr)
|
||||||
|
offset = page*limit - limit
|
||||||
|
}
|
||||||
|
files := object.GetFiles(GetUserName(user), limit, offset)
|
||||||
|
fileNum := fileNumResp{Num: object.GetFilesNum(GetUserName(user)), MaxNum: object.GetMemberFileQuota(user)}
|
||||||
|
|
||||||
|
c.ResponseOk(files, fileNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetFileNum() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
num := fileNumResp{Num: object.GetFilesNum(GetUserName(user)), MaxNum: object.GetMemberFileQuota(user)}
|
||||||
|
resp := Response{Status: "ok", Msg: "success", Data: num}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) AddFileRecord() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
var file NewUploadFile
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &file)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp Response
|
||||||
|
uploadFileNum := object.GetFilesNum(GetUserName(user))
|
||||||
|
if uploadFileNum >= object.GetMemberFileQuota(user) {
|
||||||
|
resp = Response{Status: "fail", Msg: "You have exceeded the upload limit."}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
record := object.UploadFileRecord{
|
||||||
|
FileName: file.FileName,
|
||||||
|
FilePath: file.FilePath,
|
||||||
|
FileUrl: file.FileUrl,
|
||||||
|
FileType: util.FileType(file.FileName),
|
||||||
|
FileExt: util.FileExt(file.FileName),
|
||||||
|
MemberId: GetUserName(user),
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Size: file.Size,
|
||||||
|
Deleted: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, id := object.AddFileRecord(&record)
|
||||||
|
if affected {
|
||||||
|
fileNum := fileNumResp{Num: object.GetFilesNum(GetUserName(user)), MaxNum: object.GetMemberFileQuota(user)}
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: id, Data2: fileNum}
|
||||||
|
} else {
|
||||||
|
resp = Response{Status: "fail", Msg: "Add file failed, please try again.", Data: id}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) DeleteFile() {
|
||||||
|
idStr := c.Input().Get("id")
|
||||||
|
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
id := util.ParseInt(idStr)
|
||||||
|
fileInfo := object.GetFile(id)
|
||||||
|
if !object.FileEditable(user, fileInfo.MemberId) {
|
||||||
|
c.ResponseError("Permission denied.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected := object.DeleteFileRecord(id)
|
||||||
|
var resp Response
|
||||||
|
if affected {
|
||||||
|
service.DeleteFileFromStorage(fileInfo.FilePath)
|
||||||
|
fileNum := fileNumResp{Num: object.GetFilesNum(GetUserName(user)), MaxNum: object.GetMemberFileQuota(user)}
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: id, Data2: fileNum}
|
||||||
|
} else {
|
||||||
|
resp = Response{Status: "fail", Msg: "Delete file failed, please try again."}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetFile() {
|
||||||
|
idStr := c.Input().Get("id")
|
||||||
|
|
||||||
|
id := util.ParseInt(idStr)
|
||||||
|
file := object.GetFile(id)
|
||||||
|
var resp Response
|
||||||
|
if file == nil || file.Deleted {
|
||||||
|
resp = Response{Status: "error", Msg: "No such file."}
|
||||||
|
} else {
|
||||||
|
object.AddFileViewsNum(id) // together with add file views num
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: file}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) UpdateFileDescribe() {
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
id := util.ParseInt(c.Input().Get("id"))
|
||||||
|
|
||||||
|
var desc fileDescribe
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &desc)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp Response
|
||||||
|
file := object.GetFile(id)
|
||||||
|
if !object.FileEditable(user, file.MemberId) {
|
||||||
|
resp = Response{Status: "fail", Msg: "Permission denied."}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
res := object.UpdateFileDescribe(id, desc.FileName, desc.Desc)
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: res}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title UploadFile
|
||||||
|
// @Tag File API
|
||||||
|
// @router /upload-file [post]
|
||||||
|
func (c *ApiController) UploadFile() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
memberId := c.GetSessionUsername()
|
||||||
|
|
||||||
|
fileBase64 := c.Ctx.Request.Form.Get("file")
|
||||||
|
fileType := c.Ctx.Request.Form.Get("type")
|
||||||
|
fileName := c.Ctx.Request.Form.Get("name")
|
||||||
|
index := strings.Index(fileBase64, ",")
|
||||||
|
fileBytes, _ := base64.StdEncoding.DecodeString(fileBase64[index+1:])
|
||||||
|
fileUrl, _ := service.UploadFileToStorage(memberId, "file", "UploadFile", fmt.Sprintf("casnode/file/%s/%s.%s", memberId, fileName, fileType), fileBytes)
|
||||||
|
|
||||||
|
resp := Response{Status: "ok", Msg: fileName + "." + fileType, Data: fileUrl}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title ModeratorUpload
|
||||||
|
// @Tag File API
|
||||||
|
// @router /upload-moderator [post]
|
||||||
|
func (c *ApiController) ModeratorUpload() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
if !user.IsAdmin {
|
||||||
|
c.ResponseError("You have no permission to upload files here. Need to be moderator.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileBase64 := c.Ctx.Request.Form.Get("file")
|
||||||
|
fileName := c.Ctx.Request.Form.Get("name")
|
||||||
|
filePath := c.Ctx.Request.Form.Get("filepath")
|
||||||
|
index := strings.Index(fileBase64, ",")
|
||||||
|
fileBytes, _ := base64.StdEncoding.DecodeString(fileBase64[index+1:])
|
||||||
|
fileUrl, _ := service.UploadFileToStorage(user.Name, "file", "ModeratorUpload", fmt.Sprintf("casnode/file/%s/%s/%s", user.Name, filePath, fileName), fileBytes)
|
||||||
|
timeStamp := fmt.Sprintf("?time=%d", time.Now().UnixNano())
|
||||||
|
|
||||||
|
c.ResponseOk(fileUrl + timeStamp)
|
||||||
|
// resp := Response{Status: "ok", Msg: fileName, Data: fileUrl + timeStamp}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Title GetFrontConfById
|
||||||
|
// @Description Get front conf by id
|
||||||
|
// @Success 200 {object} object.FrontConf The Response object
|
||||||
|
// @router /get-front-conf-by-id [get]
|
||||||
|
// @Tag FrontConf API
|
||||||
|
func (c *ApiController) GetFrontConfById() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
conf := object.GetFrontConfById(id)
|
||||||
|
c.ResponseOk(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetFrontConfsByField
|
||||||
|
// @Description Get front confs by field
|
||||||
|
// @Success 200 {array} object.FrontConf The Response object
|
||||||
|
// @router /get-front-confs-by-field [get]
|
||||||
|
// @Tag FrontConf API
|
||||||
|
func (c *ApiController) GetFrontConfsByField() {
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
|
||||||
|
confs := object.GetFrontConfsByField(field)
|
||||||
|
c.ResponseOk(confs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /update-front-conf-by-id [post]
|
||||||
|
// @Tag FrontConf API
|
||||||
|
// @Title UpdateFrontConfById
|
||||||
|
func (c *ApiController) UpdateFrontConfById() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
// get from body
|
||||||
|
var value string
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &value)
|
||||||
|
tags := service.Finalword(value)
|
||||||
|
affect, err := object.UpdateFrontConfById(id, value, tags)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
}
|
||||||
|
c.ResponseOk(affect)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /update-front-confs-by-filed [post]
|
||||||
|
// @Tag FrontConf API
|
||||||
|
// @Title UpdateFrontConfsByField
|
||||||
|
func (c *ApiController) UpdateFrontConfsByField() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filed := c.Input().Get("field")
|
||||||
|
var confs []*object.FrontConf
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &confs)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = object.UpdateFrontConfsByField(confs, filed)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
}
|
||||||
|
c.ResponseOk(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /restore-front-confs [post]
|
||||||
|
// @Tag FrontConf API
|
||||||
|
// @Title RestoreFrontConfs
|
||||||
|
func (c *ApiController) RestoreFrontConfs() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filed := c.Input().Get("field")
|
||||||
|
res := object.UpdateFrontConfsByField(object.Confs, filed)
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Tag Hot API
|
||||||
|
// @Title ChangeExpiredDataStatus
|
||||||
|
// @router /update-expired-data [post]
|
||||||
|
func (c *ApiController) ChangeExpiredDataStatus() {
|
||||||
|
expiredNodeDate := util.GetTimeMonth(-object.NodeHitRecordExpiredTime)
|
||||||
|
expiredTopicDate := util.GetTimeDay(-object.TopicHitRecordExpiredTime)
|
||||||
|
|
||||||
|
updateNodeNum := object.ChangeExpiredDataStatus(1, expiredNodeDate)
|
||||||
|
updateTopicNum := object.ChangeExpiredDataStatus(2, expiredTopicDate)
|
||||||
|
|
||||||
|
c.Data["json"] = Response{Status: "ok", Data: updateNodeNum, Data2: updateTopicNum}
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tag Hot API
|
||||||
|
// @Title UpdateHotInfo
|
||||||
|
// @router /update-hot-info [post]
|
||||||
|
func (c *ApiController) UpdateHotInfo() {
|
||||||
|
var updateNodeNum int
|
||||||
|
var updateTopicNum int
|
||||||
|
last := object.GetLastRecordId()
|
||||||
|
latest := object.GetLatestSyncedRecordId()
|
||||||
|
if last != latest {
|
||||||
|
object.UpdateLatestSyncedRecordId(last)
|
||||||
|
updateNodeNum = object.UpdateHotNode(latest)
|
||||||
|
updateTopicNum = object.UpdateHotTopic(latest)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = Response{Status: "ok", Data: updateNodeNum, Data2: updateTopicNum}
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Tag Info API
|
||||||
|
// @Title GetCommunityHealth
|
||||||
|
// @router /get-community-health [get]
|
||||||
|
func (c *ApiController) GetCommunityHealth() {
|
||||||
|
var memberCount int
|
||||||
|
var topicCount int
|
||||||
|
var replyCount int
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(3)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
memberCount = object.GetMemberNum()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
topicCount = object.GetTopicCount()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
replyCount = object.GetReplyCount()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
res := object.CommunityHealth{
|
||||||
|
Member: memberCount,
|
||||||
|
Topic: topicCount,
|
||||||
|
Reply: replyCount,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tag Info API
|
||||||
|
// @Title GetForumVersion
|
||||||
|
// @router /get-forum-version [get]
|
||||||
|
func (c *ApiController) GetForumVersion() {
|
||||||
|
var resp Response
|
||||||
|
|
||||||
|
res := object.GetForumVersion()
|
||||||
|
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: res}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tag Info API
|
||||||
|
// @Title GetOnlineNum
|
||||||
|
// @router /get-online-num [get]
|
||||||
|
func (c *ApiController) GetOnlineNum() {
|
||||||
|
onlineNum := object.GetOnlineMemberNum()
|
||||||
|
highest := object.GetHighestOnlineNum()
|
||||||
|
|
||||||
|
c.ResponseOk(onlineNum, highest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tag Info API
|
||||||
|
// @Title GetNodeNavigation
|
||||||
|
// @router /node-navigation [get]
|
||||||
|
func (c *ApiController) GetNodeNavigation() {
|
||||||
|
var resp Response
|
||||||
|
|
||||||
|
res := object.GetNodeNavigation()
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: res}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Title GetMember
|
||||||
|
// @Description get member by id
|
||||||
|
// @Param id query string true "id"
|
||||||
|
// @Success 200 {object} casdoorsdk.User The Response object
|
||||||
|
// @router /get-member [get]
|
||||||
|
// @Tag Member API
|
||||||
|
func (c *ApiController) GetMember() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.ResponseOk(object.GetUser(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetMemberEditorType
|
||||||
|
// @Description member editortype
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /get-member-editor-type [get]
|
||||||
|
// @Tag Member API
|
||||||
|
func (c *ApiController) GetMemberEditorType() {
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
editorType := ""
|
||||||
|
if user != nil {
|
||||||
|
editorType = object.GetMemberEditorType(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(editorType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetRankingRich
|
||||||
|
// @Description RankingRich
|
||||||
|
// @Success 200 {array} casdoorsdk.User The Response object
|
||||||
|
// @router /get-ranking-rich [get]
|
||||||
|
// @Tag Member API
|
||||||
|
func (c *ApiController) GetRankingRich() {
|
||||||
|
users, err := object.GetRankingRich()
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetRankingPlayer
|
||||||
|
// @Description RankingPlayer
|
||||||
|
// @Success 200 {array} casdoorsdk.User The Response object
|
||||||
|
// @router /get-ranking-player [get]
|
||||||
|
// @Tag Member API
|
||||||
|
func (c *ApiController) GetRankingPlayer() {
|
||||||
|
users, err := object.GetRankingPlayer()
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tag Member API
|
||||||
|
// @Title UpdateMemberEditorType
|
||||||
|
// @router /update-member-editor-type [post]
|
||||||
|
func (c *ApiController) UpdateMemberEditorType() {
|
||||||
|
editorType := c.Input().Get("editorType")
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
if editorType != "markdown" && editorType != "richtext" {
|
||||||
|
c.ResponseError(fmt.Errorf("unsupported editor type: %s", editorType).Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := object.UpdateMemberEditorType(user, editorType)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(affected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tag Member API
|
||||||
|
// @Title UpdateMemberLanguage
|
||||||
|
// @router /update-member-language [post]
|
||||||
|
func (c *ApiController) UpdateMemberLanguage() {
|
||||||
|
language := c.Input().Get("language")
|
||||||
|
|
||||||
|
if language != "zh" && language != "en" {
|
||||||
|
c.ResponseError(fmt.Errorf("unsupported language: %s", language).Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
if user == nil {
|
||||||
|
c.ResponseOk()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Language = language
|
||||||
|
c.SetSessionUser(user)
|
||||||
|
|
||||||
|
affected, err := object.UpdateMemberLanguage(user, language)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(affected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetMemberLanguage
|
||||||
|
// @Description MemberLanguage
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /get-member-language [get]
|
||||||
|
// @Tag Member API
|
||||||
|
func (c *ApiController) GetMemberLanguage() {
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
language := ""
|
||||||
|
if user != nil {
|
||||||
|
language = object.GetMemberLanguage(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(language)
|
||||||
|
}
|
|
@ -0,0 +1,270 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Title GetNodes
|
||||||
|
// @router /get-nodes [get]
|
||||||
|
// @Tag Node API
|
||||||
|
func (c *ApiController) GetNodes() {
|
||||||
|
c.Data["json"] = object.GetNodes()
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetNodesAdmin
|
||||||
|
// @router /get-nodes-admin [get]
|
||||||
|
// @Tag Node API
|
||||||
|
func (c *ApiController) GetNodesAdmin() {
|
||||||
|
res := []adminNodeInfo{}
|
||||||
|
nodes := object.GetNodes()
|
||||||
|
for _, v := range nodes {
|
||||||
|
node := adminNodeInfo{
|
||||||
|
NodeInfo: *v,
|
||||||
|
TopicNum: object.GetNodeTopicNum(v.Id),
|
||||||
|
FavoritesNum: object.GetNodeFavoritesNum(v.Id),
|
||||||
|
}
|
||||||
|
res = append(res, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = res
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetNode
|
||||||
|
// @router /get-node [get]
|
||||||
|
// @Tag Node API
|
||||||
|
func (c *ApiController) GetNode() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetNode(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title UpdateNode
|
||||||
|
// @router /update-node [post]
|
||||||
|
// @Tag Node API
|
||||||
|
func (c *ApiController) UpdateNode() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var node object.Node
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
res := object.UpdateNode(id, &node)
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title AddNode
|
||||||
|
// @router /add-node [post]
|
||||||
|
// @Tag Node API
|
||||||
|
func (c *ApiController) AddNode() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var node object.Node
|
||||||
|
var resp Response
|
||||||
|
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.Id == "" || node.Name == "" || node.TabId == "" || node.PlaneId == "" {
|
||||||
|
resp = Response{Status: "fail", Msg: "Some information is missing"}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if object.HasNode(node.Id) {
|
||||||
|
resp = Response{Status: "fail", Msg: "Node ID existed"}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
node.CreatedTime = util.GetCurrentTime()
|
||||||
|
res := object.AddNode(&node)
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title DeleteNode
|
||||||
|
// @router /delete-node [post]
|
||||||
|
// @Tag Node API
|
||||||
|
func (c *ApiController) DeleteNode() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.Data["json"] = object.DeleteNode(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetNodesNum
|
||||||
|
// @router /get-nodes-num [get]
|
||||||
|
// @Tag Node API
|
||||||
|
func (c *ApiController) GetNodesNum() {
|
||||||
|
num := object.GetNodesNum()
|
||||||
|
c.ResponseOk(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetNodeInfo
|
||||||
|
// @router /get-node-info [get]
|
||||||
|
// @Tag Node API
|
||||||
|
func (c *ApiController) GetNodeInfo() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
num := object.GetNodeTopicNum(id)
|
||||||
|
favoriteNum := object.GetNodeFavoritesNum(id)
|
||||||
|
c.ResponseOk(num, favoriteNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetNodeFromTab() {
|
||||||
|
tab := c.Input().Get("tab")
|
||||||
|
|
||||||
|
nodes := object.GetNodeFromTab(tab)
|
||||||
|
c.ResponseOk(nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetNodeRelation
|
||||||
|
// @router /get-node-relation [get]
|
||||||
|
// @Tag Node API
|
||||||
|
func (c *ApiController) GetNodeRelation() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
res := object.GetNodeRelation(id)
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tag Node API
|
||||||
|
// @router /get-latest-node [get]
|
||||||
|
// @Title GetLatestNode
|
||||||
|
func (c *ApiController) GetLatestNode() {
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
defaultLimit := object.LatestNodeNum
|
||||||
|
|
||||||
|
var limit int
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit = util.ParseInt(limitStr)
|
||||||
|
} else {
|
||||||
|
limit = defaultLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
res := object.GetLatestNode(limit)
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetHotNod
|
||||||
|
// @Tag Node API
|
||||||
|
// @router /get-hot-node [get]
|
||||||
|
func (c *ApiController) GetHotNode() {
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
defaultLimit := object.HotNodeNum
|
||||||
|
|
||||||
|
var limit int
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit = util.ParseInt(limitStr)
|
||||||
|
} else {
|
||||||
|
limit = defaultLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
res := object.GetHotNode(limit)
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title AddNodeBrowseCount
|
||||||
|
// @Tag Node API
|
||||||
|
// @router /add-node-browse-record [post]
|
||||||
|
func (c *ApiController) AddNodeBrowseCount() {
|
||||||
|
nodeId := c.Input().Get("id")
|
||||||
|
|
||||||
|
var resp Response
|
||||||
|
hitRecord := object.BrowseRecord{
|
||||||
|
MemberId: c.GetSessionUsername(),
|
||||||
|
RecordType: 1,
|
||||||
|
ObjectId: nodeId,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Expired: false,
|
||||||
|
}
|
||||||
|
res := object.AddBrowseRecordNum(&hitRecord)
|
||||||
|
if res {
|
||||||
|
c.ResponseOk()
|
||||||
|
} else {
|
||||||
|
resp = Response{Status: "fail", Msg: "add node hit count failed"}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title AddNodeModerators
|
||||||
|
// @Tag Node API
|
||||||
|
// @router /add-node-moderators [post]
|
||||||
|
func (c *ApiController) AddNodeModerators() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var moderators addNodeModerator
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &moderators)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
moderator := object.GetUser(moderators.MemberId)
|
||||||
|
if moderator == nil {
|
||||||
|
c.ResponseError("Member doesn't exist.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := object.AddNodeModerators(moderators.MemberId, moderators.NodeId)
|
||||||
|
if res {
|
||||||
|
c.ResponseOk(res)
|
||||||
|
} else {
|
||||||
|
c.ResponseError("Moderator already exist.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title DeleteNodeModerators
|
||||||
|
// @Tag Node API
|
||||||
|
// @router /delete-node-moderators [post]
|
||||||
|
func (c *ApiController) DeleteNodeModerators() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var moderators deleteNodeModerator
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &moderators)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := object.DeleteNodeModerators(moderators.MemberId, moderators.NodeId)
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ApiController) AddNotification() {
|
||||||
|
var tempNotification newNotification
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tempNotification)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
memberId := c.GetSessionUsername()
|
||||||
|
notification := object.Notification{
|
||||||
|
// Id: util.IntToString(object.GetNotificationId()),
|
||||||
|
NotificationType: tempNotification.NotificationType,
|
||||||
|
ObjectId: tempNotification.ObjectId,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
SenderId: memberId,
|
||||||
|
ReceiverId: tempNotification.ReceiverId,
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp Response
|
||||||
|
if notification.NotificationType <= 6 && notification.NotificationType >= 1 {
|
||||||
|
res := object.AddNotification(¬ification)
|
||||||
|
if !res {
|
||||||
|
resp = Response{Status: "fail", Msg: "add notification wrong"}
|
||||||
|
} else {
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: res}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resp = Response{Status: "fail", Msg: "param wrong"}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /get-notifications [get]
|
||||||
|
// @Title GetNotifications
|
||||||
|
// @Tag Notification API
|
||||||
|
func (c *ApiController) GetNotifications() {
|
||||||
|
memberId := c.GetSessionUsername()
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
pageStr := c.Input().Get("page")
|
||||||
|
defaultLimit := object.DefaultNotificationPageNum
|
||||||
|
|
||||||
|
var limit, offset int
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit = util.ParseInt(limitStr)
|
||||||
|
} else {
|
||||||
|
limit = defaultLimit
|
||||||
|
}
|
||||||
|
if len(pageStr) != 0 {
|
||||||
|
page := util.ParseInt(pageStr)
|
||||||
|
offset = page*limit - limit
|
||||||
|
}
|
||||||
|
|
||||||
|
res := object.GetNotifications(memberId, limit, offset)
|
||||||
|
num := object.GetNotificationNum(memberId)
|
||||||
|
c.ResponseOk(res, num)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title DeleteNotification
|
||||||
|
// @router /delete-notifications [get]
|
||||||
|
// @Tag Notification API
|
||||||
|
func (c *ApiController) DeleteNotification() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
res := object.DeleteNotification(id)
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetUnreadNotificationNum
|
||||||
|
// @router /get-unread-notification-num [post]
|
||||||
|
// @Tag Notification API
|
||||||
|
func (c *ApiController) GetUnreadNotificationNum() {
|
||||||
|
memberId := c.GetSessionUsername()
|
||||||
|
|
||||||
|
res := object.GetUnreadNotificationNum(memberId)
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title UpdateReadStatus
|
||||||
|
// @router /update-read-status [post]
|
||||||
|
// @Tag Notification API
|
||||||
|
func (c *ApiController) UpdateReadStatus() {
|
||||||
|
memberId := c.GetSessionUsername()
|
||||||
|
|
||||||
|
c.Data["json"] = object.UpdateReadStatus(memberId)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ApiController) GetPlanes() {
|
||||||
|
c.Data["json"] = object.GetPlanes()
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetPlanesAdmin
|
||||||
|
// @router /get-planes-admin [get]
|
||||||
|
// @Tag Plane API
|
||||||
|
func (c *ApiController) GetPlanesAdmin() {
|
||||||
|
c.Data["json"] = object.GetAllPlanes()
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetPlane
|
||||||
|
// @router /get-plane [get]
|
||||||
|
// @Tag Plane API
|
||||||
|
func (c *ApiController) GetPlane() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetPlane(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetPlaneAdmin
|
||||||
|
// @router /get-planes-admin [get]
|
||||||
|
// @Tag Plane API
|
||||||
|
func (c *ApiController) GetPlaneAdmin() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetPlaneAdmin(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetPlaneLis
|
||||||
|
// @router /get-plane-list [get]
|
||||||
|
// @Tag Plane API
|
||||||
|
func (c *ApiController) GetPlaneList() {
|
||||||
|
c.ResponseOk(object.GetPlaneList())
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title AddPlane
|
||||||
|
// @router /add-plane [post]
|
||||||
|
// @Tag Plane API
|
||||||
|
func (c *ApiController) AddPlane() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var plane object.AdminPlaneInfo
|
||||||
|
var resp Response
|
||||||
|
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plane)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if plane.Id == "" || plane.Name == "" {
|
||||||
|
resp = Response{Status: "fail", Msg: "Some information is missing"}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if object.HasPlane(plane.Id) {
|
||||||
|
resp = Response{Status: "fail", Msg: "Plane ID existed"}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newPlane := object.Plane{
|
||||||
|
Id: plane.Id,
|
||||||
|
Name: plane.Name,
|
||||||
|
Sorter: plane.Sorter,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Image: plane.Image,
|
||||||
|
BackgroundColor: plane.BackgroundColor,
|
||||||
|
Color: plane.Color,
|
||||||
|
Visible: plane.Visible,
|
||||||
|
}
|
||||||
|
res := object.AddPlane(&newPlane)
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title UpdatePlane
|
||||||
|
// @router /update-plane [post]
|
||||||
|
// @Tag Plane API
|
||||||
|
func (c *ApiController) UpdatePlane() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var plane object.AdminPlaneInfo
|
||||||
|
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plane)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
newPlane := object.Plane{
|
||||||
|
Id: plane.Id,
|
||||||
|
Name: plane.Name,
|
||||||
|
Sorter: plane.Sorter,
|
||||||
|
CreatedTime: plane.CreatedTime,
|
||||||
|
Image: plane.Image,
|
||||||
|
BackgroundColor: plane.BackgroundColor,
|
||||||
|
Color: plane.Color,
|
||||||
|
Visible: plane.Visible,
|
||||||
|
}
|
||||||
|
res := object.UpdatePlane(id, &newPlane)
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title DeletePlane
|
||||||
|
// @router /delete-plane [post]
|
||||||
|
// @Tag Plane API
|
||||||
|
func (c *ApiController) DeletePlane() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.Data["json"] = Response{Status: "ok", Msg: "success", Data: object.DeletePlane(id)}
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Title UpdatePoster
|
||||||
|
// @Description update poster message
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-poster [post]
|
||||||
|
// @Tag Poster API
|
||||||
|
func (c *ApiController) UpdatePoster() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var tempposter object.Poster
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tempposter)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
object.UpdatePoster(tempposter.Id, tempposter)
|
||||||
|
|
||||||
|
c.Data["json"] = Response{Status: "ok", Msg: "success"}
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title ReadPoster
|
||||||
|
// @Description get poster by id
|
||||||
|
// @Param id query string true "id"
|
||||||
|
// @Success 200 {object} object.Poster The Response object
|
||||||
|
// @router /read-poster [get]
|
||||||
|
// @Tag Poster API
|
||||||
|
func (c *ApiController) ReadPoster() {
|
||||||
|
n := c.Input().Get("id")
|
||||||
|
res := object.GetPoster(n)
|
||||||
|
|
||||||
|
c.Data["json"] = res
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
|
@ -0,0 +1,257 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/service"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NewReplyForm struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
TopicId int `json:"topicId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetReplies
|
||||||
|
// @Tag Reply API
|
||||||
|
// @router /get-replies [get]
|
||||||
|
func (c *ApiController) GetReplies() {
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
topicIdStr := c.Input().Get("topicId")
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
pageStr := c.Input().Get("page")
|
||||||
|
initStatus := c.Input().Get("init")
|
||||||
|
|
||||||
|
topicId := util.ParseInt(topicIdStr)
|
||||||
|
|
||||||
|
var limit, page int
|
||||||
|
repliesNum := object.GetTopicReplyNum(topicId)
|
||||||
|
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit = util.ParseInt(limitStr)
|
||||||
|
} else {
|
||||||
|
c.Data["json"] = Response{Status: "error", Msg: "Parameter missing: limit"}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(pageStr) != 0 {
|
||||||
|
if initStatus == "false" {
|
||||||
|
page = util.ParseInt(pageStr)
|
||||||
|
} else {
|
||||||
|
page = (repliesNum-1)/limit + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replies, realPage := object.GetReplies(topicId, user, limit, page)
|
||||||
|
if replies == nil {
|
||||||
|
replies = []*object.ReplyWithAvatar{}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = Response{Status: "ok", Msg: "success", Data: replies, Data2: []int{repliesNum, realPage}}
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetAllRepliesOfTopic
|
||||||
|
// @Tag Reply API
|
||||||
|
// @router /get-replies-of-topic [get]
|
||||||
|
func (c *ApiController) GetAllRepliesOfTopic() {
|
||||||
|
topicId := util.ParseInt(c.Input().Get("topicId"))
|
||||||
|
replies := object.GetRepliesOfTopic(topicId)
|
||||||
|
c.Data["json"] = Response{Status: "ok", Msg: "success", Data: replies, Data2: len(replies)}
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetReply
|
||||||
|
// @Tag Reply API
|
||||||
|
// @router /get-reply [get]
|
||||||
|
func (c *ApiController) GetReply() {
|
||||||
|
idStr := c.Input().Get("id")
|
||||||
|
|
||||||
|
id := util.ParseInt(idStr)
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetReply(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetReplyWithDetails
|
||||||
|
// @Tag Reply API
|
||||||
|
// @router /get-reply-with-details [get]
|
||||||
|
func (c *ApiController) GetReplyWithDetails() {
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
idStr := c.Input().Get("id")
|
||||||
|
|
||||||
|
id := util.ParseInt(idStr)
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetReplyWithDetails(user, id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title UpdateReply
|
||||||
|
// @Tag Reply API
|
||||||
|
// @router /update-reply [post]
|
||||||
|
func (c *ApiController) UpdateReply() {
|
||||||
|
idStr := c.Input().Get("id")
|
||||||
|
|
||||||
|
var reply object.Reply
|
||||||
|
id := util.ParseInt(idStr)
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &reply)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = object.UpdateReply(id, &reply)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title AddReply
|
||||||
|
// @Tag Reply API
|
||||||
|
// @router /add-reply [post]
|
||||||
|
func (c *ApiController) AddReply() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
if object.IsForbidden(user) {
|
||||||
|
c.ResponseError("Your account has been forbidden to perform this operation")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
balance := object.GetMemberBalance(user)
|
||||||
|
if balance < object.CreateReplyCost {
|
||||||
|
c.ResponseError("You don't have enough balance.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reply := object.Reply{
|
||||||
|
Author: GetUserName(user),
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Deleted: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &reply)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.Tags = service.Finalword(reply.Content)
|
||||||
|
|
||||||
|
previousReply := object.GetReplyByContentAndAuthor(reply.Content, reply.Author)
|
||||||
|
if previousReply != nil {
|
||||||
|
c.ResponseError("You have same reply before.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if object.ContainsSensitiveWord(reply.Content) {
|
||||||
|
c.ResponseError("Reply contains sensitive word.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, id := object.AddReply(&reply)
|
||||||
|
if affected {
|
||||||
|
object.GetReplyBonus(object.GetTopicAuthor(reply.TopicId), user, id)
|
||||||
|
object.CreateReplyConsumption(user, id)
|
||||||
|
|
||||||
|
c.UpdateAccountBalance(-object.CreateReplyCost)
|
||||||
|
c.UpdateAccountConsumptionSum(object.CreateReplyCost)
|
||||||
|
object.ChangeTopicReplyCount(reply.TopicId, 1)
|
||||||
|
object.ChangeTopicLastReplyUser(reply.TopicId, GetUserName(user), util.GetCurrentTime())
|
||||||
|
object.AddReplyNotification(reply.Author, reply.Content, id, reply.TopicId)
|
||||||
|
reply.AddReplyToMailingList()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(affected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title DeleteReply
|
||||||
|
// @Tag Reply API
|
||||||
|
// @router /delete-reply [post]
|
||||||
|
func (c *ApiController) DeleteReply() {
|
||||||
|
id := util.ParseInt(c.Input().Get("id"))
|
||||||
|
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
replyInfo := object.GetReply(id)
|
||||||
|
isAdmin := object.CheckIsAdmin(user)
|
||||||
|
if !object.ReplyDeletable(replyInfo.CreatedTime, GetUserName(user), replyInfo.Author) && !isAdmin {
|
||||||
|
resp := Response{Status: "fail", Msg: "Permission denied."}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected := object.DeleteReply(id)
|
||||||
|
if affected {
|
||||||
|
object.ChangeTopicReplyCount(replyInfo.TopicId, -1)
|
||||||
|
lastReply := object.GetLatestReplyInfo(replyInfo.TopicId)
|
||||||
|
if lastReply != nil {
|
||||||
|
object.ChangeTopicLastReplyUser(replyInfo.TopicId, lastReply.Author, lastReply.CreatedTime)
|
||||||
|
} else {
|
||||||
|
object.ChangeTopicLastReplyUser(replyInfo.TopicId, "", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(affected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetLatestReplies
|
||||||
|
// @Tag Reply API
|
||||||
|
// @router /get-latest-replies [get]
|
||||||
|
func (c *ApiController) GetLatestReplies() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
pageStr := c.Input().Get("page")
|
||||||
|
defaultLimit := object.DefaultPageNum
|
||||||
|
var (
|
||||||
|
limit, offset int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit, err = strconv.Atoi(limitStr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
limit = defaultLimit
|
||||||
|
}
|
||||||
|
if len(pageStr) != 0 {
|
||||||
|
page, err := strconv.Atoi(pageStr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
offset = page*limit - limit
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetLatestReplies(id, limit, offset)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetMemberRepliesNum
|
||||||
|
// @Tag Reply API
|
||||||
|
// @router /get-member-replies-num [get]
|
||||||
|
// @Description GetRepliesNum gets member's all replies num.
|
||||||
|
func (c *ApiController) GetMemberRepliesNum() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetMemberRepliesNum(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import "github.com/casbin/casnode/object"
|
||||||
|
|
||||||
|
// @Title Search
|
||||||
|
// @router /search
|
||||||
|
// @Tag Search API
|
||||||
|
func (c *ApiController) Search() {
|
||||||
|
keyword := c.Input().Get("keyword")
|
||||||
|
|
||||||
|
if len(keyword) == 0 {
|
||||||
|
c.Data["json"] = Response{Status: "error", Msg: "missing keyword"}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
topics := object.SearchTopics(keyword)
|
||||||
|
c.Data["json"] = Response{Status: "ok", Data: topics}
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Title AddSensitive
|
||||||
|
// @router /add-sensitive [get]
|
||||||
|
// @Tag Seneistive API
|
||||||
|
func (c *ApiController) AddSensitive() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sensitiveWord := c.Input().Get("word")
|
||||||
|
if sensitiveWord == "" {
|
||||||
|
c.ResponseError("You didn't input a sensitive word.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(sensitiveWord) > 64 {
|
||||||
|
c.ResponseError("This sensitive word is too long.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if object.IsSensitiveWord(sensitiveWord) {
|
||||||
|
c.ResponseError("This is already a sensitive word.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
object.AddSensitiveWord(sensitiveWord)
|
||||||
|
|
||||||
|
c.ResponseOk()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title DelSensitive
|
||||||
|
// @router /del-sensitive [get]
|
||||||
|
// @Tag Seneistive API
|
||||||
|
func (c *ApiController) DelSensitive() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sensitiveWord := c.Input().Get("word")
|
||||||
|
if sensitiveWord == "" {
|
||||||
|
c.ResponseError("You didn't input a sensitive word.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !object.IsSensitiveWord(sensitiveWord) {
|
||||||
|
c.ResponseError("This is not a sensitive word.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
object.DeleteSensitiveWord(sensitiveWord)
|
||||||
|
|
||||||
|
c.ResponseOk()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetSensitive
|
||||||
|
// @router /get-sensitive [get]
|
||||||
|
// @Tag Seneistive API
|
||||||
|
func (c *ApiController) GetSensitive() {
|
||||||
|
c.Data["json"] = object.GetSensitiveWords()
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @router /get-tabs [get]
|
||||||
|
// @Title GetTabs
|
||||||
|
// @Tag Tab API
|
||||||
|
func (c *ApiController) GetTabs() {
|
||||||
|
c.Data["json"] = object.GetHomePageTabs()
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /get-all-tabs [get]
|
||||||
|
// @Title GetAllTabs
|
||||||
|
// @Tag Tab API
|
||||||
|
func (c *ApiController) GetAllTabs() {
|
||||||
|
c.Data["json"] = object.GetAllTabs()
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /get-tabs-admin [get]
|
||||||
|
// @Title GetAllTabsAdmin
|
||||||
|
// @Tag Tab API
|
||||||
|
func (c *ApiController) GetAllTabsAdmin() {
|
||||||
|
c.Data["json"] = object.GetAllTabsAdmin()
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /get-tabs-admin [get]
|
||||||
|
// @Title GetTabAdmin
|
||||||
|
// @Tag Tab API
|
||||||
|
func (c *ApiController) GetTabAdmin() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetTabAdmin(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /get-tabs-admin [get]
|
||||||
|
// @Title AddTab
|
||||||
|
// @Tag Tab API
|
||||||
|
func (c *ApiController) AddTab() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var tabInfo object.AdminTabInfo
|
||||||
|
var resp Response
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tabInfo)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tabInfo.Id == "" || tabInfo.Name == "" || tabInfo.Sorter <= 0 {
|
||||||
|
resp = Response{Status: "fail", Msg: "Some information is missing"}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if object.HasTab(tabInfo.Id) {
|
||||||
|
resp = Response{Status: "fail", Msg: "Tab ID existed"}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tab := object.Tab{
|
||||||
|
Id: tabInfo.Id,
|
||||||
|
Name: tabInfo.Name,
|
||||||
|
Sorter: tabInfo.Sorter,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
DefaultNode: tabInfo.DefaultNode,
|
||||||
|
HomePage: tabInfo.HomePage,
|
||||||
|
}
|
||||||
|
|
||||||
|
res := object.AddTab(&tab)
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /update-tab [post]
|
||||||
|
// @Title UpdateTab
|
||||||
|
// @Tag Tab API
|
||||||
|
func (c *ApiController) UpdateTab() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var tabInfo object.AdminTabInfo
|
||||||
|
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tabInfo)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tab := object.Tab{
|
||||||
|
// Id: tabInfo.Id,
|
||||||
|
Name: tabInfo.Name,
|
||||||
|
Sorter: tabInfo.Sorter,
|
||||||
|
CreatedTime: tabInfo.CreatedTime,
|
||||||
|
DefaultNode: tabInfo.DefaultNode,
|
||||||
|
HomePage: tabInfo.HomePage,
|
||||||
|
}
|
||||||
|
res := object.UpdateTab(id, &tab)
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /delete-tab [post]
|
||||||
|
// @Title DeleteTab
|
||||||
|
// @Tag Tab API
|
||||||
|
func (c *ApiController) DeleteTab() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
resp := Response{Status: "ok", Msg: "success", Data: object.DeleteTab(id)}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /get-tab-with-nodes [get]
|
||||||
|
// @Title GetTabWithNodes
|
||||||
|
// @Tag Tab API
|
||||||
|
func (c *ApiController) GetTabWithNodes() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
if len(id) == 0 {
|
||||||
|
id = object.GetDefaultTab()
|
||||||
|
}
|
||||||
|
|
||||||
|
tabInfo := object.GetTab(id)
|
||||||
|
nodes := object.GetNodesByTab(id)
|
||||||
|
resp := Response{Status: "ok", Msg: "success", Data: tabInfo, Data2: nodes}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetTabNodes() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
if len(id) == 0 {
|
||||||
|
id = object.GetDefaultTab()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetNodesByTab(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIE+TCCAuGgAwIBAgIDAeJAMA0GCSqGSIb3DQEBCwUAMDYxHTAbBgNVBAoTFENh
|
||||||
|
c2Rvb3IgT3JnYW5pemF0aW9uMRUwEwYDVQQDEwxDYXNkb29yIENlcnQwHhcNMjEx
|
||||||
|
MDE1MDgxMTUyWhcNNDExMDE1MDgxMTUyWjA2MR0wGwYDVQQKExRDYXNkb29yIE9y
|
||||||
|
Z2FuaXphdGlvbjEVMBMGA1UEAxMMQ2FzZG9vciBDZXJ0MIICIjANBgkqhkiG9w0B
|
||||||
|
AQEFAAOCAg8AMIICCgKCAgEAsInpb5E1/ym0f1RfSDSSE8IR7y+lw+RJjI74e5ej
|
||||||
|
rq4b8zMYk7HeHCyZr/hmNEwEVXnhXu1P0mBeQ5ypp/QGo8vgEmjAETNmzkI1NjOQ
|
||||||
|
CjCYwUrasO/f/MnI1C0j13vx6mV1kHZjSrKsMhYY1vaxTEP3+VB8Hjg3MHFWrb07
|
||||||
|
uvFMCJe5W8+0rKErZCKTR8+9VB3janeBz//zQePFVh79bFZate/hLirPK0Go9P1g
|
||||||
|
OvwIoC1A3sarHTP4Qm/LQRt0rHqZFybdySpyWAQvhNaDFE7mTstRSBb/wUjNCUBD
|
||||||
|
PTSLVjC04WllSf6Nkfx0Z7KvmbPstSj+btvcqsvRAGtvdsB9h62Kptjs1Yn7GAuo
|
||||||
|
I3qt/4zoKbiURYxkQJXIvwCQsEftUuk5ew5zuPSlDRLoLByQTLbx0JqLAFNfW3g/
|
||||||
|
pzSDjgd/60d6HTmvbZni4SmjdyFhXCDb1Kn7N+xTojnfaNkwep2REV+RMc0fx4Gu
|
||||||
|
hRsnLsmkmUDeyIZ9aBL9oj11YEQfM2JZEq+RVtUx+wB4y8K/tD1bcY+IfnG5rBpw
|
||||||
|
IDpS262boq4SRSvb3Z7bB0w4ZxvOfJ/1VLoRftjPbLIf0bhfr/AeZMHpIKOXvfz4
|
||||||
|
yE+hqzi68wdF0VR9xYc/RbSAf7323OsjYnjjEgInUtRohnRgCpjIk/Mt2Kt84Kb0
|
||||||
|
wn8CAwEAAaMQMA4wDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAn2lf
|
||||||
|
DKkLX+F1vKRO/5gJ+Plr8P5NKuQkmwH97b8CS2gS1phDyNgIc4/LSdzuf4Awe6ve
|
||||||
|
C06lVdWSIis8UPUPdjmT2uMPSNjwLxG3QsrimMURNwFlLTfRem/heJe0Zgur9J1M
|
||||||
|
8haawdSdJjH2RgmFoDeE2r8NVRfhbR8KnCO1ddTJKuS1N0/irHz21W4jt4rxzCvl
|
||||||
|
2nR42Fybap3O/g2JXMhNNROwZmNjgpsF7XVENCSuFO1jTywLaqjuXCg54IL7XVLG
|
||||||
|
omKNNNcc8h1FCeKj/nnbGMhodnFWKDTsJcbNmcOPNHo6ixzqMy/Hqc+mWYv7maAG
|
||||||
|
Jtevs3qgMZ8F9Qzr3HpUc6R3ZYYWDY/xxPisuKftOPZgtH979XC4mdf0WPnOBLqL
|
||||||
|
2DJ1zaBmjiGJolvb7XNVKcUfDXYw85ZTZQ5b9clI4e+6bmyWqQItlwt+Ati/uFEV
|
||||||
|
XzCj70B4lALX6xau1kLEpV9O1GERizYRz5P9NJNA7KoO5AVMp9w0DQTkt+LbXnZE
|
||||||
|
HHnWKy8xHQKZF9sR7YBPGLs/Ac6tviv5Ua15OgJ/8dLRZ/veyFfGo2yZsI+hKVU5
|
||||||
|
nCCJHBcAyFnm1hdvdwEdH33jDBjNB6ciotJZrf/3VYaIWSalADosHAgMWfXuWP+h
|
||||||
|
8XKXmzlxuHbTMQYtZPDgspS5aK+S4Q9wb8RRAYo=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,854 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/service"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NewTopicForm struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
NodeId string `json:"nodeId"`
|
||||||
|
EditorType string `json:"editorType"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetTopics
|
||||||
|
// @Description get current topics
|
||||||
|
// @Param limit query string true "topics size"
|
||||||
|
// @Param page query string true "offset"
|
||||||
|
// @Success 200 {array} object.TopicWithAvatar The Response object
|
||||||
|
// @router /get-topics [get]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) GetTopics() {
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
pageStr := c.Input().Get("page")
|
||||||
|
defaultLimit := object.DefaultHomePageNum
|
||||||
|
|
||||||
|
var limit, offset int
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit = util.ParseInt(limitStr)
|
||||||
|
} else {
|
||||||
|
limit = defaultLimit
|
||||||
|
}
|
||||||
|
if len(pageStr) != 0 {
|
||||||
|
page := util.ParseInt(pageStr)
|
||||||
|
offset = page*limit - limit
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetTopics(limit, offset)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetTopicsAdmin
|
||||||
|
// @Description get topics for admin
|
||||||
|
// @Param limit query string true "topics size"
|
||||||
|
// @Param page query string true "offset"
|
||||||
|
// @Param un query string true "username(author)"
|
||||||
|
// @Param ti query string true "search: title"
|
||||||
|
// @Param cn query string true "search: content"
|
||||||
|
// @Param sdt query string true "sort: show deleted topics"
|
||||||
|
// @Param cs query string true "sort: created time"
|
||||||
|
// @Param lrs query string true "sort: last reply time"
|
||||||
|
// @Param us query string true "sort: username"
|
||||||
|
// @Param rcs query string true "sort: reply count"
|
||||||
|
// @Param hs query string true "sort: hot"
|
||||||
|
// @Param fcs query string true "sort: favorite count"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /get-topics-admin [get]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) GetTopicsAdmin() {
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
pageStr := c.Input().Get("page")
|
||||||
|
|
||||||
|
usernameSearchKw := c.Input().Get("un") // search: username(author)
|
||||||
|
titleSearchKw := c.Input().Get("ti") // search: title
|
||||||
|
contentSearchKw := c.Input().Get("cn") // search: content
|
||||||
|
|
||||||
|
showDeletedTopics := c.Input().Get("sdt") // sort: show deleted topics
|
||||||
|
|
||||||
|
createdTimeSort := c.Input().Get("cs") // sort: created time
|
||||||
|
lastReplySort := c.Input().Get("lrs") // sort: last reply time
|
||||||
|
usernameSort := c.Input().Get("us") // sort: username
|
||||||
|
replyCountSort := c.Input().Get("rcs") // sort: reply count
|
||||||
|
hotSort := c.Input().Get("hs") // sort: hot
|
||||||
|
favCountSort := c.Input().Get("fcs") // sort: favorite count
|
||||||
|
|
||||||
|
defaultLimit := object.DefaultHomePageNum
|
||||||
|
|
||||||
|
var limit, offset int
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit = util.ParseInt(limitStr)
|
||||||
|
} else {
|
||||||
|
limit = defaultLimit
|
||||||
|
}
|
||||||
|
if len(pageStr) != 0 {
|
||||||
|
page := util.ParseInt(pageStr)
|
||||||
|
offset = page*limit - limit
|
||||||
|
}
|
||||||
|
|
||||||
|
res, num := object.GetTopicsAdmin(usernameSearchKw, titleSearchKw, contentSearchKw, showDeletedTopics, createdTimeSort, lastReplySort, usernameSort, replyCountSort, hotSort, favCountSort, limit, offset)
|
||||||
|
|
||||||
|
c.Data["json"] = Response{Status: "ok", Msg: "success", Data: res, Data2: num}
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetTopic
|
||||||
|
// @Description get one topic by id
|
||||||
|
// @Param id query string true "id"
|
||||||
|
// @Success 200 {object} object.TopicWithAvatar The Response object
|
||||||
|
// @router /get-topic [get]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) GetTopic() {
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
id := util.ParseInt(c.Input().Get("id"))
|
||||||
|
|
||||||
|
topic := object.GetTopicWithAvatar(id, user)
|
||||||
|
if topic == nil || topic.Deleted {
|
||||||
|
c.Data["json"] = nil
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user != nil {
|
||||||
|
topic.NodeModerator = object.CheckNodeModerator(user, topic.NodeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = topic
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetTopicAdmin
|
||||||
|
// @Description get topic for admin by id
|
||||||
|
// @Param id query string true "id"
|
||||||
|
// @Success 200 {object} object.AdminTopicInfo The Response object
|
||||||
|
// @router /get-topic-admin [get]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) GetTopicAdmin() {
|
||||||
|
idStr := c.Input().Get("id")
|
||||||
|
|
||||||
|
id := util.ParseInt(idStr)
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetTopicAdmin(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) UpdateTopic() {
|
||||||
|
idStr := c.Input().Get("id")
|
||||||
|
|
||||||
|
var topic object.Topic
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id := util.ParseInt(idStr)
|
||||||
|
c.Data["json"] = object.UpdateTopic(id, &topic)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title AddTopic
|
||||||
|
// @Description add one topic
|
||||||
|
// @Param form body controllers.NewTopicForm true "topic info"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-topic [post]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) AddTopic() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
if object.IsForbidden(user) {
|
||||||
|
c.ResponseError("Your account has been forbidden to perform this operation")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var form NewTopicForm
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
title, body, nodeId, editorType, tags := form.Title, form.Body, form.NodeId, form.EditorType, form.Tags
|
||||||
|
|
||||||
|
node := object.GetNode(nodeId)
|
||||||
|
if node == nil {
|
||||||
|
c.ResponseError("Node does not exist.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if object.ContainsSensitiveWord(title) {
|
||||||
|
c.ResponseError("Topic title contains sensitive word.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if object.ContainsSensitiveWord(body) {
|
||||||
|
c.ResponseError("Topic body contains sensitive word.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tags) == 0 {
|
||||||
|
tags = service.Finalword(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
topic := object.Topic{
|
||||||
|
// Id: util.IntToString(object.GetTopicId()),
|
||||||
|
Author: GetUserName(user),
|
||||||
|
NodeId: node.Id,
|
||||||
|
NodeName: node.Name,
|
||||||
|
TabId: node.TabId,
|
||||||
|
Title: title,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Tags: tags,
|
||||||
|
LastReplyUser: "",
|
||||||
|
LastReplyTime: util.GetCurrentTime(),
|
||||||
|
UpCount: 0,
|
||||||
|
DownCount: 0,
|
||||||
|
HitCount: 0,
|
||||||
|
FavoriteCount: 0,
|
||||||
|
SubscribeCount: 0,
|
||||||
|
Content: body,
|
||||||
|
Deleted: false,
|
||||||
|
EditorType: editorType,
|
||||||
|
IsHidden: node.IsHidden,
|
||||||
|
}
|
||||||
|
|
||||||
|
balance := object.GetMemberBalance(user)
|
||||||
|
if balance < object.CreateTopicCost {
|
||||||
|
c.ResponseError("You don't have enough balance.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// payRes := object.CreateTopicConsumption(c.GetSessionUser(), topic.Id)
|
||||||
|
|
||||||
|
// object.AddTopicNotification(topic.Id, c.GetSessionUser(), body)
|
||||||
|
|
||||||
|
err = json.Unmarshal(c.Ctx.Input.RequestBody, &topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
topics := object.GetTopicsByTitleAndAuthor(topic.Title, topic.Author)
|
||||||
|
if len(topics) != 0 {
|
||||||
|
c.ResponseError("Duplicate topic")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res, id := object.AddTopic(&topic)
|
||||||
|
if res {
|
||||||
|
object.CreateTopicConsumption(user, id)
|
||||||
|
|
||||||
|
c.UpdateAccountBalance(-object.CreateTopicCost)
|
||||||
|
c.UpdateAccountConsumptionSum(object.CreateTopicCost)
|
||||||
|
object.AddTopicNotification(id, topic.Author, topic.Content)
|
||||||
|
targetNode := object.GetNode(topic.NodeId)
|
||||||
|
targetNode.AddTopicToMailingList(topic.Title, topic.Content, topic.Author)
|
||||||
|
|
||||||
|
c.ResponseOk(topic.Id)
|
||||||
|
} else {
|
||||||
|
c.ResponseError("Failed to add topic.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title UploadTopicPic
|
||||||
|
// @Description upload topic picture
|
||||||
|
// @Param pic formData string true "the picture base64 code"
|
||||||
|
// @Param type formData string true "the picture type"
|
||||||
|
// @Success 200 {object} _controllers.Response The Response object
|
||||||
|
// @router /upload-topic-pic [post]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) UploadTopicPic() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
memberId := c.GetSessionUsername()
|
||||||
|
fileBase64 := c.Ctx.Request.Form.Get("pic")
|
||||||
|
fileType := c.Ctx.Request.Form.Get("type")
|
||||||
|
index := strings.Index(fileBase64, ",")
|
||||||
|
fileBytes, _ := base64.StdEncoding.DecodeString(fileBase64[index+1:])
|
||||||
|
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
||||||
|
fileUrl, _ := service.UploadFileToStorage(memberId, "topicPic", "UploadTopicPic", fmt.Sprintf("casnode/topicPic/%s/%s.%s", memberId, timestamp, fileType), fileBytes)
|
||||||
|
|
||||||
|
resp := Response{Status: "ok", Msg: timestamp + "." + fileType, Data: fileUrl}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title DeleteTopic
|
||||||
|
// @Description delete a topic by id
|
||||||
|
// @Param id query string true "topic id"
|
||||||
|
// @Success 200 {bool} bool Delete success or failure
|
||||||
|
// @router /delete-topic [post]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) DeleteTopic() {
|
||||||
|
idStr := c.Input().Get("id")
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
id := util.ParseInt(idStr)
|
||||||
|
nodeId := object.GetTopicNodeId(id)
|
||||||
|
if !object.CheckIsAdmin(user) && !object.CheckNodeModerator(user, nodeId) {
|
||||||
|
resp := Response{Status: "fail", Msg: "Unauthorized."}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = object.DeleteTopic(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetTopicsNum
|
||||||
|
// @Description get the total number of topics
|
||||||
|
// @Success 200 {int} int The topic nums
|
||||||
|
// @router /get-topics-num [get]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) GetTopicsNum() {
|
||||||
|
c.Data["json"] = object.GetTopicNum()
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetAllCreatedTopics
|
||||||
|
// @Description get all created topics
|
||||||
|
// @Param id query string true "author id"
|
||||||
|
// @Param tab query string true "tab"
|
||||||
|
// @Param limit query string true "mumber of topics"
|
||||||
|
// @Param page query string true "page offset"
|
||||||
|
// @Success 200 {array} object.Topic The Response object
|
||||||
|
// @router /get-all-created-topics [get]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) GetAllCreatedTopics() {
|
||||||
|
author := c.Input().Get("id")
|
||||||
|
tab := c.Input().Get("tab")
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
pageStr := c.Input().Get("page")
|
||||||
|
var (
|
||||||
|
limit, offset int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit, err = strconv.Atoi(limitStr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
if len(pageStr) != 0 {
|
||||||
|
page, err := strconv.Atoi(pageStr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
offset = page*limit - limit
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetAllCreatedTopics(author, tab, limit, offset)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetCreatedTopicsNum
|
||||||
|
// @Description get created topics count
|
||||||
|
// @Param id query string true "member id"
|
||||||
|
// @Success 200 {int} int topics count
|
||||||
|
// @router /get-created-topics-num [get]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) GetCreatedTopicsNum() {
|
||||||
|
memberId := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetCreatedTopicsNum(memberId)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetTopicsByNode
|
||||||
|
// @Description get topics by node
|
||||||
|
// @Param node-id query string true "node id"
|
||||||
|
// @Param limit query string true "number of topics"
|
||||||
|
// @Param page query string true "page offset"
|
||||||
|
// @Success 200 {array} object.NodeTopic The Response object
|
||||||
|
// @router /get-topics-by-node [get]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) GetTopicsByNode() {
|
||||||
|
nodeId := c.Input().Get("node-id")
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
pageStr := c.Input().Get("page")
|
||||||
|
defaultLimit := object.DefaultPageNum
|
||||||
|
|
||||||
|
var limit, offset int
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit = util.ParseInt(limitStr)
|
||||||
|
} else {
|
||||||
|
limit = defaultLimit
|
||||||
|
}
|
||||||
|
if len(pageStr) != 0 {
|
||||||
|
page := util.ParseInt(pageStr)
|
||||||
|
offset = page*limit - limit
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetTopicsByNode(nodeId, limit, offset)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetTopicsByTag
|
||||||
|
// @Description get topics by tag
|
||||||
|
// @Param tag-id query string true "tag id"
|
||||||
|
// @Param limit query string true "number of topics"
|
||||||
|
// @Param page query string true "page offset"
|
||||||
|
// @Success 200 {array} object.NodeTopic The Response object
|
||||||
|
// @router /get-topics-by-tag [get]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) GetTopicsByTag() {
|
||||||
|
tagId := c.Input().Get("tag-id")
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
pageStr := c.Input().Get("page")
|
||||||
|
defaultLimit := object.DefaultPageNum
|
||||||
|
|
||||||
|
var limit, offset int
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit = util.ParseInt(limitStr)
|
||||||
|
} else {
|
||||||
|
limit = defaultLimit
|
||||||
|
}
|
||||||
|
if len(pageStr) != 0 {
|
||||||
|
page := util.ParseInt(pageStr)
|
||||||
|
offset = page*limit - limit
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetTopicsByTag(tagId, limit, offset)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title AddTopicHitCount
|
||||||
|
// @Description add topic hit count,together with node
|
||||||
|
// @Param id query string true "topic id"
|
||||||
|
// @Success 200 {object} controller.Response The Response object
|
||||||
|
// @router /add-topic-hit-count [post]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) AddTopicHitCount() {
|
||||||
|
topicIdStr := c.Input().Get("id")
|
||||||
|
|
||||||
|
var resp Response
|
||||||
|
topicId := util.ParseInt(topicIdStr)
|
||||||
|
res := object.AddTopicHitCount(topicId)
|
||||||
|
topicInfo := object.GetTopic(topicId)
|
||||||
|
hitRecord := object.BrowseRecord{
|
||||||
|
MemberId: c.GetSessionUsername(),
|
||||||
|
RecordType: 1,
|
||||||
|
ObjectId: topicInfo.NodeId,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Expired: false,
|
||||||
|
}
|
||||||
|
object.AddBrowseRecordNum(&hitRecord)
|
||||||
|
if res {
|
||||||
|
resp = Response{Status: "ok", Msg: "success"}
|
||||||
|
} else {
|
||||||
|
resp = Response{Status: "fail", Msg: "add topic hit count failed"}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetTopicsByTab
|
||||||
|
// @Description get topics by tab
|
||||||
|
// @Param tab-id query string true "tab id"
|
||||||
|
// @Param limit query string true "number of topics"
|
||||||
|
// @Param page query string true "page offset"
|
||||||
|
// @Success 200 {array} object.TopicWithAvatar The Response object
|
||||||
|
// @router /get-topics-by-tab [get]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) GetTopicsByTab() {
|
||||||
|
tabId := c.Input().Get("tab-id")
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
pageStr := c.Input().Get("page")
|
||||||
|
defaultLimit := object.DefaultHomePageNum
|
||||||
|
|
||||||
|
var limit, offset int
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit = util.ParseInt(limitStr)
|
||||||
|
} else {
|
||||||
|
limit = defaultLimit
|
||||||
|
}
|
||||||
|
if len(pageStr) != 0 {
|
||||||
|
page := util.ParseInt(pageStr)
|
||||||
|
offset = page*limit - limit
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetTopicsWithTab(tabId, limit, offset)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title AddTopicBrowseCount
|
||||||
|
// @Description add topic browse count
|
||||||
|
// @Param id query string true "topicId"
|
||||||
|
// @Success 200 {object} controller.Response The Response object
|
||||||
|
// @router /add-topic-browse-record [post]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) AddTopicBrowseCount() {
|
||||||
|
topicId := c.Input().Get("id")
|
||||||
|
|
||||||
|
var resp Response
|
||||||
|
hitRecord := object.BrowseRecord{
|
||||||
|
MemberId: c.GetSessionUsername(),
|
||||||
|
RecordType: 2,
|
||||||
|
ObjectId: topicId,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Expired: false,
|
||||||
|
}
|
||||||
|
res := object.AddBrowseRecordNum(&hitRecord)
|
||||||
|
if res {
|
||||||
|
resp = Response{Status: "ok", Msg: "success"}
|
||||||
|
} else {
|
||||||
|
resp = Response{Status: "fail", Msg: "add node hit count failed"}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetHotTopic
|
||||||
|
// @Description get hot topic
|
||||||
|
// @Param limit query string true "limit size"
|
||||||
|
// @Success 200 {object} controller.Response The Response object
|
||||||
|
// @router /get-hot-topic [get]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) GetHotTopic() {
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
defaultLimit := object.HotTopicNum
|
||||||
|
|
||||||
|
var limit int
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit = util.ParseInt(limitStr)
|
||||||
|
} else {
|
||||||
|
limit = defaultLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
res := object.GetHotTopic(limit)
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetSortedTopics
|
||||||
|
// @Description get sorted topics
|
||||||
|
// @Param lps query string true "sort: last reply count"
|
||||||
|
// @Param hs query string true "sort: hot"
|
||||||
|
// @Param fcs query string true "sort: favorite count"
|
||||||
|
// @Param cts query string true "sort: created time"
|
||||||
|
// @Param page query string true "offset"
|
||||||
|
// @Param limit query string true "limit size"
|
||||||
|
// @Success 200 {object} controller.Response The Response object
|
||||||
|
// @router /get-hot-topic [get]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) GetSortedTopics() {
|
||||||
|
limitStr := c.Input().Get("limit")
|
||||||
|
pageStr := c.Input().Get("page")
|
||||||
|
lastReplySort := c.Input().Get("lps") // sort: last reply time
|
||||||
|
hotSort := c.Input().Get("hs") // sort: hot
|
||||||
|
favCountSort := c.Input().Get("fcs") // sort: favorite count
|
||||||
|
createdTimeSort := c.Input().Get("cts") // sort: created time
|
||||||
|
|
||||||
|
defaultLimit := object.DefaultHomePageNum
|
||||||
|
|
||||||
|
var limit, offset int
|
||||||
|
if len(limitStr) != 0 {
|
||||||
|
limit = util.ParseInt(limitStr)
|
||||||
|
} else {
|
||||||
|
limit = defaultLimit
|
||||||
|
}
|
||||||
|
if len(pageStr) != 0 {
|
||||||
|
page := util.ParseInt(pageStr)
|
||||||
|
offset = page*limit - limit
|
||||||
|
}
|
||||||
|
|
||||||
|
res := object.GetSortedTopics(lastReplySort, hotSort, favCountSort, createdTimeSort, limit, offset)
|
||||||
|
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title UpdateTopicNode
|
||||||
|
// @Description update the topic node
|
||||||
|
// @Param updateTopicNode body controllers.updateTopicNode true "topic node info"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-topic-node [post]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) UpdateTopicNode() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
var form updateTopicNode
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
id, _, nodeId := form.Id, form.NodeName, form.NodeId
|
||||||
|
|
||||||
|
originalNode := object.GetTopicNodeId(id)
|
||||||
|
if !object.CheckIsAdmin(user) && !object.CheckNodeModerator(user, originalNode) && object.GetTopicAuthor(id).Name != GetUserName(user) {
|
||||||
|
c.ResponseError("Unauthorized.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
node := object.GetNode(nodeId)
|
||||||
|
if node == nil {
|
||||||
|
c.ResponseError("Node does not exist.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
topic := object.Topic{
|
||||||
|
// Id: id,
|
||||||
|
NodeId: node.Id,
|
||||||
|
NodeName: node.Name,
|
||||||
|
TabId: node.TabId,
|
||||||
|
}
|
||||||
|
res := object.UpdateTopicWithLimitCols(id, &topic)
|
||||||
|
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title EditContent
|
||||||
|
// @Description edit content
|
||||||
|
// @Param editType query string true "edit Type"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /edit-content [post]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) EditContent() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
editType := c.Input().Get("editType")
|
||||||
|
var resp Response
|
||||||
|
if editType == "topic" {
|
||||||
|
var form editTopic
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
id, title, content, nodeId, editorType, tags := form.Id, form.Title, form.Content, form.NodeId, form.EditorType, form.Tags
|
||||||
|
if !object.CheckIsAdmin(user) && !object.CheckNodeModerator(user, nodeId) && object.GetTopicAuthor(id).Name != GetUserName(user) {
|
||||||
|
resp = Response{Status: "fail", Msg: "Unauthorized."}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
topic := object.Topic{
|
||||||
|
Id: id,
|
||||||
|
Title: title,
|
||||||
|
Content: content,
|
||||||
|
EditorType: editorType,
|
||||||
|
Tags: tags,
|
||||||
|
}
|
||||||
|
res := object.UpdateTopicWithLimitCols(id, &topic)
|
||||||
|
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: res}
|
||||||
|
} else {
|
||||||
|
var form editReply
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
id, content, editorType := form.Id, form.Content, form.EditorType
|
||||||
|
if !object.CheckIsAdmin(user) && object.GetReplyAuthor(id).Name != GetUserName(user) {
|
||||||
|
resp = Response{Status: "fail", Msg: "Unauthorized."}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reply := object.Reply{
|
||||||
|
Id: id,
|
||||||
|
Content: content,
|
||||||
|
EditorType: editorType,
|
||||||
|
}
|
||||||
|
res := object.UpdateReplyWithLimitCols(id, &reply)
|
||||||
|
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: res}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title TranslateTopic
|
||||||
|
// @router /translate-topic [get]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) TranslateTopic() {
|
||||||
|
topicIdStr := c.Input().Get("id")
|
||||||
|
targetLang := c.Input().Get("target")
|
||||||
|
|
||||||
|
// ISO/IEC 15897 to ISO 639-1
|
||||||
|
targetLang = targetLang[0:2]
|
||||||
|
|
||||||
|
topicId := util.ParseInt(topicIdStr)
|
||||||
|
|
||||||
|
translateData := &object.TranslateData{}
|
||||||
|
|
||||||
|
topic := object.GetTopic(topicId)
|
||||||
|
if topic == nil || topic.Deleted {
|
||||||
|
translateData.ErrMsg = "Invalid TopicId"
|
||||||
|
c.Data["json"] = translateData
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = object.StrTranslate(topic.Content, targetLang)
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TopTopic tops topic according to the topType in the url.
|
||||||
|
// @Title TopTopic
|
||||||
|
// @Description tops topic according to the topType in the url.
|
||||||
|
// @Param id query string true "id"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /top-topic [post]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) TopTopic() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := util.ParseInt(c.Input().Get("id"))
|
||||||
|
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
var res bool
|
||||||
|
nodeId := object.GetTopicNodeId(id)
|
||||||
|
if object.CheckIsAdmin(user) || object.CheckNodeModerator(user, nodeId) {
|
||||||
|
// timeStr := c.Input().Get("time")
|
||||||
|
// time := util.ParseInt(timeStr)
|
||||||
|
// date := util.GetTimeMinute(time)
|
||||||
|
// res = object.ChangeTopicTopExpiredTime(id, date)
|
||||||
|
topType := c.Input().Get("topType")
|
||||||
|
date := util.GetTimeYear(100)
|
||||||
|
res = object.ChangeTopicTopExpiredTime(id, date, topType)
|
||||||
|
} else if object.GetTopicAuthor(id).Name == GetUserName(user) {
|
||||||
|
balance := object.GetMemberBalance(user)
|
||||||
|
if balance < object.TopTopicCost {
|
||||||
|
c.ResponseError("You don't have enough balance.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
object.TopTopicConsumption(user, id)
|
||||||
|
|
||||||
|
c.UpdateAccountBalance(-object.TopTopicCost)
|
||||||
|
|
||||||
|
date := util.GetTimeMinute(object.DefaultTopTopicTime)
|
||||||
|
res = object.ChangeTopicTopExpiredTime(id, date, "node")
|
||||||
|
} else {
|
||||||
|
c.ResponseError("Unauthorized.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title CancelTopTopic
|
||||||
|
// @Description cancels top topic according to the topType in the url.
|
||||||
|
// @Param id query string true "id"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /cancel-top-topic [post]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) CancelTopTopic() {
|
||||||
|
if c.RequireSignedIn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idStr := c.Input().Get("id")
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
|
||||||
|
id := util.ParseInt(idStr)
|
||||||
|
var resp Response
|
||||||
|
var res bool
|
||||||
|
|
||||||
|
nodeId := object.GetTopicNodeId(id)
|
||||||
|
if object.CheckIsAdmin(user) || object.CheckNodeModerator(user, nodeId) {
|
||||||
|
topType := c.Input().Get("topType")
|
||||||
|
res = object.ChangeTopicTopExpiredTime(id, "", topType)
|
||||||
|
} else {
|
||||||
|
resp = Response{Status: "fail", Msg: "Unauthorized."}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = Response{Status: "ok", Msg: "success", Data: res}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetTopicByUrlPathAndTitle
|
||||||
|
// @router /get-topic-by-urlpath-and-title [get]
|
||||||
|
// @Tag Topic API
|
||||||
|
func (c *ApiController) GetTopicByUrlPathAndTitle() {
|
||||||
|
urlPath := c.Input().Get("urlPath")
|
||||||
|
title := c.Input().Get("title")
|
||||||
|
author := c.Input().Get("author")
|
||||||
|
nodeId := c.Input().Get("nodeId")
|
||||||
|
|
||||||
|
if urlPath == "" {
|
||||||
|
c.ResponseError(fmt.Sprintf("The urlPath: %s does not exist", urlPath))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if title == "" {
|
||||||
|
c.ResponseError(fmt.Sprintf("The title: %s does not exist", title))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if author == "" {
|
||||||
|
c.ResponseError(fmt.Sprintf("The author: %s does not exist", author))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
node := object.GetNode(nodeId)
|
||||||
|
if nodeId == "" || node == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("The node: %s does not exist", nodeId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
topic := object.GetTopicByUrlPathAndTitle(urlPath, title, nodeId)
|
||||||
|
if topic == nil {
|
||||||
|
topic = &object.Topic{
|
||||||
|
Author: author,
|
||||||
|
NodeId: nodeId,
|
||||||
|
NodeName: node.Name,
|
||||||
|
TabId: node.TabId,
|
||||||
|
Title: title,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
LastReplyTime: util.GetCurrentTime(),
|
||||||
|
Content: fmt.Sprintf("URL: %s%s", nodeId, urlPath),
|
||||||
|
UrlPath: urlPath,
|
||||||
|
EditorType: "markdown",
|
||||||
|
IsHidden: node.IsHidden,
|
||||||
|
}
|
||||||
|
object.AddTopic(topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(topic)
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @router /update-translator [post]
|
||||||
|
// @Title UpdateTranslator
|
||||||
|
// @Tag Translator API
|
||||||
|
func (c *ApiController) UpdateTranslator() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var translator object.Translator
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &translator)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
object.UpdateTranslator(translator)
|
||||||
|
|
||||||
|
c.ResponseOk()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /add-translator [post]
|
||||||
|
// @Title AddTranslator
|
||||||
|
// @Tag Translator API
|
||||||
|
func (c *ApiController) AddTranslator() {
|
||||||
|
if c.RequireAdmin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp Response
|
||||||
|
var translator object.Translator
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &translator)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := object.GetTranslator(translator.Id)
|
||||||
|
if len(*res) > 0 {
|
||||||
|
resp = Response{Status: "fail", Msg: "Translator ID existed"}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if object.AddTranslator(translator) {
|
||||||
|
c.ResponseOk()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
resp = Response{Status: "fail", Msg: "Add translator failed"}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /get-translator [get]
|
||||||
|
// @Title GetTranslator
|
||||||
|
// @Tag Translator API
|
||||||
|
func (c *ApiController) GetTranslator() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
res := object.GetTranslator(id)
|
||||||
|
|
||||||
|
c.Data["json"] = res
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetEnableTranslator() {
|
||||||
|
res := object.GetEnableTranslator()
|
||||||
|
|
||||||
|
c.Data["json"] = res
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /visible-translator [get]
|
||||||
|
// @Title VisibleTranslator
|
||||||
|
// @Tag Translator API
|
||||||
|
func (c *ApiController) VisibleTranslator() {
|
||||||
|
c.ResponseOk(false)
|
||||||
|
|
||||||
|
res := object.GetEnableTranslator()
|
||||||
|
if res != nil {
|
||||||
|
if res.Visible {
|
||||||
|
c.ResponseOk(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /del-translator [post]
|
||||||
|
// @Title DelTranslator
|
||||||
|
// @Tag Translator API
|
||||||
|
func (c *ApiController) DelTranslator() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
resp := Response{Status: "fail", Msg: "Delete translator failed"}
|
||||||
|
if object.DelTranslator(id) {
|
||||||
|
resp = Response{Status: "ok", Msg: "Success"}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import "github.com/casbin/casnode/object"
|
||||||
|
|
||||||
|
type GetAccessTokenRespFromWeChat struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn float64 `json:"expires_in"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
Openid string `json:"openid"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type authResponse struct {
|
||||||
|
IsAuthenticated bool `json:"isAuthenticated"`
|
||||||
|
IsSignedUp bool `json:"isSignedUp"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
Addition string `json:"addition"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type newNotification struct {
|
||||||
|
ObjectId int `json:"objectId"`
|
||||||
|
NotificationType int `json:"notificationType"`
|
||||||
|
ReceiverId string `json:"receiverId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type updateTopicNode struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
NodeId string `json:"nodeId"`
|
||||||
|
NodeName string `json:"nodeName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type editTopic struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
NodeId string `json:"nodeId"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
EditorType string `json:"editorType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type editReply struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
EditorType string `json:"editorType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileDescribe struct {
|
||||||
|
Desc string `json:"desc"`
|
||||||
|
FileName string `json:"fileName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileNumResp struct {
|
||||||
|
Num int `json:"num"`
|
||||||
|
MaxNum int `json:"maxNum"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type addNodeModerator struct {
|
||||||
|
NodeId string `json:"nodeId"`
|
||||||
|
MemberId string `json:"memberId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type deleteNodeModerator struct {
|
||||||
|
NodeId string `json:"nodeId"`
|
||||||
|
MemberId string `json:"memberId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type adminNodeInfo struct {
|
||||||
|
NodeInfo object.Node `json:"nodeInfo"`
|
||||||
|
TopicNum int `json:"topicNum"`
|
||||||
|
FavoritesNum int `json:"favoritesNum"`
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
Data2 interface{} `json:"data2"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) ResponseOk(data ...interface{}) {
|
||||||
|
resp := Response{Status: "ok"}
|
||||||
|
switch len(data) {
|
||||||
|
case 2:
|
||||||
|
resp.Data2 = data[1]
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
resp.Data = data[0]
|
||||||
|
}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) ResponseError(error string, data ...interface{}) {
|
||||||
|
resp := Response{Status: "error", Msg: error}
|
||||||
|
switch len(data) {
|
||||||
|
case 2:
|
||||||
|
resp.Data2 = data[1]
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
resp.Data = data[0]
|
||||||
|
}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) RequireSignedIn() bool {
|
||||||
|
if c.GetSessionUser() == nil {
|
||||||
|
c.ResponseError("please sign in first")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) RequireAdmin() bool {
|
||||||
|
user := c.GetSessionUser()
|
||||||
|
if user == nil || !user.IsAdmin {
|
||||||
|
c.ResponseError("this operation requires admin privilege")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var adapter *Adapter
|
||||||
|
|
||||||
|
func InitAdapter() {
|
||||||
|
adapter = NewAdapter(beego.AppConfig.String("driverName"), beego.AppConfig.String("dataSourceName"), dbName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapter represents the MySQL adapter for policy storage.
|
||||||
|
type Adapter struct {
|
||||||
|
driverName string
|
||||||
|
dataSourceName string
|
||||||
|
dbName string
|
||||||
|
Engine *xorm.Engine
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalizer is the destructor for Adapter.
|
||||||
|
func finalizer(a *Adapter) {
|
||||||
|
err := a.Engine.Close()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAdapter is the constructor for Adapter.
|
||||||
|
func NewAdapter(driverName string, dataSourceName string, dbName string) *Adapter {
|
||||||
|
a := &Adapter{}
|
||||||
|
a.driverName = driverName
|
||||||
|
a.dataSourceName = dataSourceName
|
||||||
|
a.dbName = dbName
|
||||||
|
|
||||||
|
// Open the DB, create it if not existed.
|
||||||
|
a.open()
|
||||||
|
|
||||||
|
// Call the destructor when the object is released.
|
||||||
|
runtime.SetFinalizer(a, finalizer)
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) open() {
|
||||||
|
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName+a.dbName)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Engine = engine
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) close() {
|
||||||
|
a.Engine.Close()
|
||||||
|
a.Engine = nil
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Attachment struct {
|
||||||
|
Tid int
|
||||||
|
Pid int
|
||||||
|
Uid int
|
||||||
|
Dateline int
|
||||||
|
Filename string
|
||||||
|
Filesize int
|
||||||
|
Attachment string
|
||||||
|
Description string
|
||||||
|
Isimage int
|
||||||
|
Width int
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAttachmentsInTable(tableIndex int) []*Attachment {
|
||||||
|
attachments := []*Attachment{}
|
||||||
|
tableName := fmt.Sprintf("pre_forum_attachment_%d", tableIndex)
|
||||||
|
err := adapter.Engine.Table(tableName).Find(&attachments)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return attachments
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAttachments() []*Attachment {
|
||||||
|
attachments := []*Attachment{}
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
tmp := getAttachmentsInTable(i)
|
||||||
|
attachments = append(attachments, tmp...)
|
||||||
|
}
|
||||||
|
return attachments
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAttachmentMap() map[int][]*Attachment {
|
||||||
|
attachments := getAttachments()
|
||||||
|
m := map[int][]*Attachment{}
|
||||||
|
|
||||||
|
for _, attachment := range attachments {
|
||||||
|
if _, ok := m[attachment.Tid]; !ok {
|
||||||
|
m[attachment.Tid] = []*Attachment{}
|
||||||
|
}
|
||||||
|
|
||||||
|
m[attachment.Tid] = append(m[attachment.Tid], attachment)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func uploadDiscuzxFile(username string, fileBytes []byte, fileName string, createdTime string, description string) string {
|
||||||
|
username = url.QueryEscape(username)
|
||||||
|
memberId := fmt.Sprintf("%s/%s", CasdoorOrganization, username)
|
||||||
|
fileUrl, _ := service.UploadFileToStorageSafe(memberId, "file", "uploadDiscuzxFile", fmt.Sprintf("file/%s/%s", memberId, fileName), fileBytes, createdTime, description)
|
||||||
|
return fileUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRecordFromAttachment(attachment *Attachment, post *Post) *object.UploadFileRecord {
|
||||||
|
oldFileUrl := fmt.Sprintf("%s%s", discuzxAttachmentBaseUrl, attachment.Attachment)
|
||||||
|
fileBytes, _, err := downloadFileSafe(oldFileUrl)
|
||||||
|
if err != nil {
|
||||||
|
if urlError, ok := err.(*url.Error); ok {
|
||||||
|
fmt.Printf("\t\t[%d]: getRecordFromAttachment() error: %s, the attachement is deleted: %s\n", post.Pid, urlError.Error(), attachment.Attachment)
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileUrl := uploadDiscuzxFile(post.Author, fileBytes, attachment.Filename, getTimeFromUnixSeconds(attachment.Dateline), attachment.Description)
|
||||||
|
|
||||||
|
fileType := "file"
|
||||||
|
if attachment.Isimage == 1 {
|
||||||
|
fileType = "image"
|
||||||
|
}
|
||||||
|
|
||||||
|
record := &object.UploadFileRecord{
|
||||||
|
FileName: attachment.Filename,
|
||||||
|
FileUrl: fileUrl,
|
||||||
|
FileType: fileType,
|
||||||
|
}
|
||||||
|
return record
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/casdoor"
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
var discuzxDefaultAvatarUrl string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
discuzxDefaultAvatarUrl = fmt.Sprintf("%suc_server/images/noavatar_middle.gif", discuzxDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncAvatarForUser(user *casdoorsdk.User) string {
|
||||||
|
uid := user.Ranking
|
||||||
|
username := user.Name
|
||||||
|
|
||||||
|
oldAvatarUrl := fmt.Sprintf("%suc_server/avatar.php?uid=%d", discuzxDomain, uid)
|
||||||
|
newAvatarUrl := getRedirectUrl(oldAvatarUrl)
|
||||||
|
if oldAvatarUrl == newAvatarUrl || newAvatarUrl == "" {
|
||||||
|
panic(fmt.Errorf("getRedirectUrl() error: oldAvatarUrl == newAvatarUrl, oldAvatarUrl = %s, newAvatarUrl = %s", oldAvatarUrl, newAvatarUrl))
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileBytes []byte
|
||||||
|
var fileExt string
|
||||||
|
|
||||||
|
if newAvatarUrl == discuzxDefaultAvatarUrl {
|
||||||
|
randomAvatarUrl := getRandomAvatarUrl(username)
|
||||||
|
fileBytes = getRandomAvatar(randomAvatarUrl)
|
||||||
|
fileExt = ".png"
|
||||||
|
|
||||||
|
user.IsDefaultAvatar = true
|
||||||
|
go casdoor.UpdateUser(user.Owner, user.Name, user)
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
times := 0
|
||||||
|
for {
|
||||||
|
fileBytes, _, err = downloadFile(newAvatarUrl)
|
||||||
|
if err != nil {
|
||||||
|
if urlError, ok := err.(*url.Error); ok {
|
||||||
|
if hostnameError, ok := urlError.Err.(x509.HostnameError); ok {
|
||||||
|
times += 1
|
||||||
|
fmt.Printf("[%d]: downloadFile() error: %s, times = %d, use random avatar\n", uid, hostnameError.Error(), times)
|
||||||
|
if times >= 10 {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
randomAvatarUrl := getRandomAvatarUrl(username)
|
||||||
|
fileBytes = getRandomAvatar(randomAvatarUrl)
|
||||||
|
fileExt = ".png"
|
||||||
|
|
||||||
|
user.IsDefaultAvatar = true
|
||||||
|
go casdoor.UpdateUser(user.Owner, user.Name, user)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
times += 1
|
||||||
|
fmt.Printf("[%d]: downloadFile() error: %s, times = %d\n", uid, err.Error(), times)
|
||||||
|
if times >= 10 {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileExt == "" {
|
||||||
|
fileExt = path.Ext(newAvatarUrl)
|
||||||
|
}
|
||||||
|
if fileExt != ".png" {
|
||||||
|
fileBytes, fileExt, err = convertImageToPng(fileBytes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
avatarUrl, err := uploadDiscuzxAvatar(username, fileBytes, fileExt)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return avatarUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateDefaultAvatarForUser(user *casdoorsdk.User) string {
|
||||||
|
uid := user.Ranking
|
||||||
|
username := user.Name
|
||||||
|
|
||||||
|
defaultAvatarUrl := getRandomAvatarUrl(username)
|
||||||
|
|
||||||
|
var fileBytes []byte
|
||||||
|
var newUrl string
|
||||||
|
var fileExt string
|
||||||
|
var err error
|
||||||
|
times := 0
|
||||||
|
for {
|
||||||
|
fileBytes, newUrl, err = downloadFile(defaultAvatarUrl)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
times += 1
|
||||||
|
fmt.Printf("[%d]: downloadFile() error: %s, times = %d\n", uid, err.Error(), times)
|
||||||
|
if times >= 10 {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExt = path.Ext(newUrl)
|
||||||
|
|
||||||
|
avatarUrl, err := uploadDiscuzxAvatar(username, fileBytes, fileExt)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return avatarUrl
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/casdoor"
|
||||||
|
"github.com/casbin/casnode/controllers"
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAvatar(t *testing.T) {
|
||||||
|
object.InitConfig()
|
||||||
|
InitAdapter()
|
||||||
|
object.InitAdapter()
|
||||||
|
casdoor.InitCasdoorAdapter()
|
||||||
|
|
||||||
|
url := "https://casbin.org/img/casbin.svg"
|
||||||
|
downloadFile(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateDefaultAvatars(t *testing.T) {
|
||||||
|
object.InitConfig()
|
||||||
|
InitAdapter()
|
||||||
|
object.InitAdapter()
|
||||||
|
casdoor.InitCasdoorAdapter()
|
||||||
|
controllers.InitAuthConfig()
|
||||||
|
|
||||||
|
users := casdoor.GetUsers()
|
||||||
|
|
||||||
|
sem := make(chan int, SyncAvatarsConcurrency)
|
||||||
|
for i, user := range users {
|
||||||
|
sem <- 1
|
||||||
|
go func(i int, user *casdoorsdk.User) {
|
||||||
|
if user.IsDefaultAvatar {
|
||||||
|
avatarUrl := updateDefaultAvatarForUser(user)
|
||||||
|
fmt.Printf("[%d/%d]: Updated default avatar for user: [%d, %s] as URL: %s\n", i+1, len(users), user.Ranking, user.Name, avatarUrl)
|
||||||
|
}
|
||||||
|
<-sem
|
||||||
|
}(i, user)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/casdoor"
|
||||||
|
"github.com/casbin/casnode/controllers"
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
var SyncAvatarsConcurrency = 20
|
||||||
|
|
||||||
|
func TestSyncAvatars(t *testing.T) {
|
||||||
|
object.InitConfig()
|
||||||
|
InitAdapter()
|
||||||
|
object.InitAdapter()
|
||||||
|
casdoor.InitCasdoorAdapter()
|
||||||
|
controllers.InitAuthConfig()
|
||||||
|
initRandomAvatars()
|
||||||
|
|
||||||
|
users := casdoor.GetUsers()
|
||||||
|
|
||||||
|
sem := make(chan int, SyncAvatarsConcurrency)
|
||||||
|
for i, user := range users {
|
||||||
|
sem <- 1
|
||||||
|
go func(i int, user *casdoorsdk.User) {
|
||||||
|
if user.Avatar == "" {
|
||||||
|
avatarUrl := syncAvatarForUser(user)
|
||||||
|
fmt.Printf("[%d/%d]: Synced avatar for user: [%d, %s] as URL: %s\n", i+1, len(users), user.Ranking, user.Name, avatarUrl)
|
||||||
|
}
|
||||||
|
<-sem
|
||||||
|
}(i, user)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"image/gif"
|
||||||
|
"image/jpeg"
|
||||||
|
"image/png"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func uploadDiscuzxAvatar(username string, fileBytes []byte, fileExt string) (string, error) {
|
||||||
|
username = url.QueryEscape(username)
|
||||||
|
memberId := fmt.Sprintf("%s/%s", CasdoorOrganization, username)
|
||||||
|
fileUrl, err := service.UploadFileToStorageSafe(memberId, "avatar", "uploadDiscuzxAvatar", fmt.Sprintf("avatar/%s%s", memberId, fileExt), fileBytes, "", "")
|
||||||
|
return fileUrl, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertImageToPng(imageBytes []byte) ([]byte, string, error) {
|
||||||
|
// Converting jpeg images to png with Golang
|
||||||
|
// https://medium.com/@daetam/converting-jpeg-to-png-with-golang-85905105cf47
|
||||||
|
|
||||||
|
contentType := http.DetectContentType(imageBytes)
|
||||||
|
|
||||||
|
switch contentType {
|
||||||
|
case "image/png":
|
||||||
|
return imageBytes, ".png", nil
|
||||||
|
case "image/jpeg":
|
||||||
|
img, err := jpeg.Decode(bytes.NewReader(imageBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err = png.Encode(buf, img)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), ".png", nil
|
||||||
|
case "image/gif":
|
||||||
|
img, err := gif.Decode(bytes.NewReader(imageBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err = png.Encode(buf, img)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), ".png", nil
|
||||||
|
default:
|
||||||
|
return nil, "", fmt.Errorf("convertImageToPng() error: unsupported contentType: %s", contentType)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var randomAvatarCount = 244
|
||||||
|
|
||||||
|
var (
|
||||||
|
randomAvatarMap map[string][]byte
|
||||||
|
randomAvatarMapMutex sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
func initRandomAvatars() {
|
||||||
|
randomAvatarMap = map[string][]byte{}
|
||||||
|
randomAvatarMapMutex = sync.RWMutex{}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(randomAvatarCount)
|
||||||
|
|
||||||
|
sem := make(chan int, 100)
|
||||||
|
for i := 1; i <= randomAvatarCount; i++ {
|
||||||
|
sem <- 1
|
||||||
|
go func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
avatarUrl := fmt.Sprintf("%s%d.png", avatarPoolBaseUrl, i)
|
||||||
|
fileBytes, _, err := downloadFile(avatarUrl)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
randomAvatarMapMutex.Lock()
|
||||||
|
randomAvatarMap[avatarUrl] = fileBytes
|
||||||
|
randomAvatarMapMutex.Unlock()
|
||||||
|
fmt.Printf("[%d/%d]: Initialized random avatar: %s\n", i, randomAvatarCount, avatarUrl)
|
||||||
|
<-sem
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRandomAvatar(avatarUrl string) []byte {
|
||||||
|
fileBytes, ok := randomAvatarMap[avatarUrl]
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("getRandomAvatar() error, key not found: %s", avatarUrl))
|
||||||
|
}
|
||||||
|
return fileBytes
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CasdoorOrganization = ""
|
||||||
|
CasdoorApplication = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
object.InitConfig()
|
||||||
|
|
||||||
|
CasdoorOrganization = beego.AppConfig.String("casdoorOrganization")
|
||||||
|
CasdoorApplication = beego.AppConfig.String("casdoorApplication")
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
type Class struct {
|
||||||
|
Typeid int
|
||||||
|
Fid int
|
||||||
|
Name string
|
||||||
|
Displayorder int
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClasses() []*Class {
|
||||||
|
classes := []*Class{}
|
||||||
|
err := adapter.Engine.Table("pre_forum_threadclass").Find(&classes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return classes
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClass(id int) *Class {
|
||||||
|
class := Class{Typeid: id}
|
||||||
|
existed, err := adapter.Engine.Table("pre_forum_threadclass").Get(&class)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &class
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClassMap() map[int]*Class {
|
||||||
|
classes := getClasses()
|
||||||
|
|
||||||
|
m := map[int]*Class{}
|
||||||
|
for _, class := range classes {
|
||||||
|
m[class.Typeid] = class
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
var (
|
||||||
|
dbName = "ultrax"
|
||||||
|
discuzxDomain = "https://www.discuz.net/"
|
||||||
|
discuzxAttachmentBaseUrl = "https://attachment.discuz.net/forum/"
|
||||||
|
)
|
||||||
|
|
||||||
|
var avatarPoolBaseUrl = "https://cdn.casbin.com/avatar-pool/"
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Forum struct {
|
||||||
|
Fid int
|
||||||
|
Fup int
|
||||||
|
Type string
|
||||||
|
Name string
|
||||||
|
Status int
|
||||||
|
Displayorder int
|
||||||
|
Threads int
|
||||||
|
|
||||||
|
Forums []*Forum `xorm:"-"`
|
||||||
|
Parent *Forum `xorm:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getForums() []*Forum {
|
||||||
|
forums := []*Forum{}
|
||||||
|
err := adapter.Engine.Table("pre_forum_forum").Find(&forums)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return forums
|
||||||
|
}
|
||||||
|
|
||||||
|
func getForum(id int) *Forum {
|
||||||
|
forum := Forum{Fid: id}
|
||||||
|
existed, err := adapter.Engine.Table("pre_forum_forum").Get(&forum)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &forum
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getForumMap() map[int]*Forum {
|
||||||
|
forums := getForums()
|
||||||
|
|
||||||
|
m := map[int]*Forum{}
|
||||||
|
for _, forum := range forums {
|
||||||
|
m[forum.Fid] = forum
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func getForumTree() ([]*Forum, map[int]*Forum) {
|
||||||
|
res := []*Forum{}
|
||||||
|
|
||||||
|
forumMap := getForumMap()
|
||||||
|
for _, forum := range forumMap {
|
||||||
|
if forum.Type == "group" {
|
||||||
|
res = append(res, forum)
|
||||||
|
} else {
|
||||||
|
parentForum := forumMap[forum.Fup]
|
||||||
|
parentForum.Forums = append(parentForum.Forums, forum)
|
||||||
|
forum.Parent = parentForum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forumNameCountMap := map[string]int{}
|
||||||
|
for _, forum := range forumMap {
|
||||||
|
if forum.Type == "group" {
|
||||||
|
forumNameCountMap[forum.Name] = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := forumNameCountMap[forum.Name]; ok {
|
||||||
|
forumNameCountMap[forum.Name] = v + 1
|
||||||
|
} else {
|
||||||
|
forumNameCountMap[forum.Name] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, forum := range forumMap {
|
||||||
|
if forumNameCountMap[forum.Name] > 1 {
|
||||||
|
parentForum := forumMap[forum.Fup]
|
||||||
|
forum.Name = fmt.Sprintf("%s-%s", parentForum.Name, forum.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, forumMap
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
type Field struct {
|
||||||
|
Fid int
|
||||||
|
Description string
|
||||||
|
Icon string
|
||||||
|
Moderators string
|
||||||
|
Rules string
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFields() []*Field {
|
||||||
|
fields := []*Field{}
|
||||||
|
err := adapter.Engine.Table("pre_forum_forumfield").Find(&fields)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
func getField(id int) *Field {
|
||||||
|
field := Field{Fid: id}
|
||||||
|
existed, err := adapter.Engine.Table("pre_forum_forumfield").Get(&field)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &field
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFieldMap() map[int]*Field {
|
||||||
|
fields := getFields()
|
||||||
|
|
||||||
|
m := map[int]*Field{}
|
||||||
|
for _, field := range fields {
|
||||||
|
m[field.Fid] = field
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getInfoFromField(field *Field) (string, string, []string) {
|
||||||
|
if field == nil {
|
||||||
|
return "", "", []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
desc := field.Description
|
||||||
|
extra := field.Rules
|
||||||
|
|
||||||
|
moderators := []string{}
|
||||||
|
if field.Moderators != "" {
|
||||||
|
moderators = strings.Split(field.Moderators, "\t")
|
||||||
|
}
|
||||||
|
|
||||||
|
return desc, extra, moderators
|
||||||
|
}
|
||||||
|
|
||||||
|
func addForums() {
|
||||||
|
tabs := []*object.Tab{}
|
||||||
|
nodes := []*object.Node{}
|
||||||
|
|
||||||
|
forumTree, _ := getForumTree()
|
||||||
|
forumFieldMap := getFieldMap()
|
||||||
|
for i, groupForum := range forumTree {
|
||||||
|
defaultNode := ""
|
||||||
|
if len(groupForum.Forums) != 0 {
|
||||||
|
defaultNode = groupForum.Forums[0].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
field := forumFieldMap[groupForum.Fid]
|
||||||
|
desc, extra, moderators := getInfoFromField(field)
|
||||||
|
|
||||||
|
tab := &object.Tab{
|
||||||
|
Id: groupForum.Name,
|
||||||
|
Name: groupForum.Name,
|
||||||
|
Sorter: groupForum.Displayorder,
|
||||||
|
Ranking: groupForum.Fid,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
DefaultNode: defaultNode,
|
||||||
|
HomePage: true,
|
||||||
|
Desc: desc,
|
||||||
|
Extra: extra,
|
||||||
|
Moderators: moderators,
|
||||||
|
}
|
||||||
|
|
||||||
|
tabs = append(tabs, tab)
|
||||||
|
fmt.Printf("[%d/%d]: Synced group forum: %s\n", i+1, len(forumTree), groupForum.Name)
|
||||||
|
|
||||||
|
for j, forum := range groupForum.Forums {
|
||||||
|
field2 := forumFieldMap[forum.Fid]
|
||||||
|
desc2, extra2, moderators2 := getInfoFromField(field2)
|
||||||
|
|
||||||
|
forumNode := &object.Node{
|
||||||
|
Id: forum.Name,
|
||||||
|
Name: forum.Name,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Desc: desc2,
|
||||||
|
Extra: extra2,
|
||||||
|
Image: "https://cdn.v2ex.com/navatar/3b8a/6142/215_xxlarge.png?m=1523190513",
|
||||||
|
TabId: groupForum.Name,
|
||||||
|
ParentNode: "",
|
||||||
|
PlaneId: "",
|
||||||
|
Sorter: forum.Displayorder,
|
||||||
|
Ranking: forum.Fid,
|
||||||
|
Hot: forum.Threads,
|
||||||
|
Moderators: moderators2,
|
||||||
|
MailingList: "",
|
||||||
|
GoogleGroupCookie: "",
|
||||||
|
IsHidden: forum.Status == 0,
|
||||||
|
}
|
||||||
|
nodes = append(nodes, forumNode)
|
||||||
|
fmt.Printf("\t[%d/%d]: Synced forum: %s\n", j+1, len(groupForum.Forums), forum.Name)
|
||||||
|
|
||||||
|
for k, subForum := range forum.Forums {
|
||||||
|
field3 := forumFieldMap[subForum.Fid]
|
||||||
|
desc3, extra3, moderators3 := getInfoFromField(field3)
|
||||||
|
|
||||||
|
subForumNode := &object.Node{
|
||||||
|
Id: subForum.Name,
|
||||||
|
Name: subForum.Name,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Desc: desc3,
|
||||||
|
Extra: extra3,
|
||||||
|
Image: "https://cdn.v2ex.com/navatar/3b8a/6142/215_xxlarge.png?m=1523190513",
|
||||||
|
TabId: groupForum.Name,
|
||||||
|
ParentNode: forum.Name,
|
||||||
|
PlaneId: "",
|
||||||
|
Sorter: subForum.Displayorder,
|
||||||
|
Ranking: subForum.Fid,
|
||||||
|
Hot: subForum.Threads,
|
||||||
|
Moderators: moderators3,
|
||||||
|
MailingList: "",
|
||||||
|
GoogleGroupCookie: "",
|
||||||
|
IsHidden: subForum.Status == 0,
|
||||||
|
}
|
||||||
|
nodes = append(nodes, subForumNode)
|
||||||
|
fmt.Printf("\t\t[%d/%d]: Synced sub forum: %s\n", k+1, len(forum.Forums), subForum.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultNode := ""
|
||||||
|
if len(nodes) > 0 {
|
||||||
|
defaultNode = nodes[0].Id
|
||||||
|
}
|
||||||
|
|
||||||
|
tab := &object.Tab{
|
||||||
|
Id: "all",
|
||||||
|
Name: "全部",
|
||||||
|
Sorter: 100,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
DefaultNode: defaultNode,
|
||||||
|
HomePage: true,
|
||||||
|
}
|
||||||
|
tabs = append(tabs, tab)
|
||||||
|
|
||||||
|
object.AddTabs(tabs)
|
||||||
|
object.AddNodes(nodes)
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddForums(t *testing.T) {
|
||||||
|
object.InitConfig()
|
||||||
|
InitAdapter()
|
||||||
|
object.InitAdapter()
|
||||||
|
|
||||||
|
addForums()
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
type Member struct {
|
||||||
|
Uid int
|
||||||
|
Email string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
Status int
|
||||||
|
Emailstatus int
|
||||||
|
Avatarstatus int
|
||||||
|
Videophotostatus int
|
||||||
|
Adminid int
|
||||||
|
Groupid int
|
||||||
|
Groupexpiry int
|
||||||
|
Extgroupids string
|
||||||
|
Regdate int
|
||||||
|
Credits int
|
||||||
|
Allowadmincp int
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMembers() []*Member {
|
||||||
|
members := []*Member{}
|
||||||
|
err := adapter.Engine.Table("pre_common_member").Find(&members)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return members
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMember(id int) *Member {
|
||||||
|
member := Member{Uid: id}
|
||||||
|
existed, err := adapter.Engine.Table("pre_common_member").Get(&member)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &member
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMemberMap() map[int]*Member {
|
||||||
|
members := getMembers()
|
||||||
|
|
||||||
|
m := map[int]*Member{}
|
||||||
|
for _, member := range members {
|
||||||
|
m[member.Uid] = member
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
type MemberEx struct {
|
||||||
|
*Member
|
||||||
|
*Profile
|
||||||
|
*UcenterMember
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMembersEx() []*MemberEx {
|
||||||
|
members := getMembers()
|
||||||
|
profileMap := getProfileMap()
|
||||||
|
ucenterMemberMap := getUcenterMemberMap()
|
||||||
|
|
||||||
|
membersEx := []*MemberEx{}
|
||||||
|
for _, member := range members {
|
||||||
|
memberEx := &MemberEx{
|
||||||
|
Member: member,
|
||||||
|
Profile: profileMap[member.Uid],
|
||||||
|
UcenterMember: ucenterMemberMap[member.Uid],
|
||||||
|
}
|
||||||
|
membersEx = append(membersEx, memberEx)
|
||||||
|
}
|
||||||
|
return membersEx
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMemberEx(id int) *MemberEx {
|
||||||
|
member := getMember(id)
|
||||||
|
profile := getProfile(id)
|
||||||
|
ucenterMember := getUcenterMember(id)
|
||||||
|
return &MemberEx{
|
||||||
|
Member: member,
|
||||||
|
Profile: profile,
|
||||||
|
UcenterMember: ucenterMember,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMemberExMap() map[int]*MemberEx {
|
||||||
|
membersEx := getMembersEx()
|
||||||
|
|
||||||
|
m := map[int]*MemberEx{}
|
||||||
|
for _, memberEx := range membersEx {
|
||||||
|
m[memberEx.Member.Uid] = memberEx
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
type Profile struct {
|
||||||
|
Uid int
|
||||||
|
Realname string
|
||||||
|
Gender int
|
||||||
|
Birthyear int
|
||||||
|
Birthmonth int
|
||||||
|
Birthday int
|
||||||
|
Mobile string
|
||||||
|
Idcardtype string
|
||||||
|
Idcard string
|
||||||
|
Address string
|
||||||
|
Resideprovince string
|
||||||
|
Residecity string
|
||||||
|
Residedist string
|
||||||
|
Residecommunity string
|
||||||
|
Education string
|
||||||
|
Occupation string
|
||||||
|
Position string
|
||||||
|
Site string
|
||||||
|
Bio string
|
||||||
|
Interest string
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProfiles() []*Profile {
|
||||||
|
profiles := []*Profile{}
|
||||||
|
err := adapter.Engine.Table("pre_common_member_profile").Find(&profiles)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return profiles
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProfile(id int) *Profile {
|
||||||
|
profile := Profile{Uid: id}
|
||||||
|
existed, err := adapter.Engine.Table("pre_common_member_profile").Get(&profile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &profile
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProfileMap() map[int]*Profile {
|
||||||
|
profiles := getProfiles()
|
||||||
|
|
||||||
|
m := map[int]*Profile{}
|
||||||
|
for _, profile := range profiles {
|
||||||
|
m[profile.Uid] = profile
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
type UcenterMember struct {
|
||||||
|
Uid int
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
Email string
|
||||||
|
Regip string
|
||||||
|
Regdate int
|
||||||
|
Lastloginip int
|
||||||
|
Lastlogintime int
|
||||||
|
Salt string
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUcenterMembers() []*UcenterMember {
|
||||||
|
ucenterMembers := []*UcenterMember{}
|
||||||
|
err := adapter.Engine.Table("pre_ucenter_members").Find(&ucenterMembers)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ucenterMembers
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUcenterMember(id int) *UcenterMember {
|
||||||
|
ucenterMember := UcenterMember{Uid: id}
|
||||||
|
existed, err := adapter.Engine.Table("pre_ucenter_members").Get(&ucenterMember)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &ucenterMember
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUcenterMemberMap() map[int]*UcenterMember {
|
||||||
|
ucenterMembers := getUcenterMembers()
|
||||||
|
|
||||||
|
m := map[int]*UcenterMember{}
|
||||||
|
for _, ucenterMember := range ucenterMembers {
|
||||||
|
m[ucenterMember.Uid] = ucenterMember
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import "github.com/casbin/casnode/object"
|
||||||
|
|
||||||
|
type Post struct {
|
||||||
|
Pid int
|
||||||
|
Tid int
|
||||||
|
First int
|
||||||
|
Author string
|
||||||
|
Subject string
|
||||||
|
Dateline int
|
||||||
|
Message string
|
||||||
|
Useip string
|
||||||
|
Invisible int
|
||||||
|
|
||||||
|
UploadFileRecords []*object.UploadFileRecord `xorm:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPosts() []*Post {
|
||||||
|
posts := []*Post{}
|
||||||
|
err := adapter.Engine.Table("pre_forum_post").Find(&posts)
|
||||||
|
// err := adapter.Engine.Table("pre_forum_post").Where("tid = ?", threadId).Find(&posts)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return posts
|
||||||
|
}
|
||||||
|
|
||||||
|
func getThreadPostsMap() (map[int][]*Post, int) {
|
||||||
|
threadPostsMap := map[int][]*Post{}
|
||||||
|
|
||||||
|
posts := getPosts()
|
||||||
|
for _, post := range posts {
|
||||||
|
tid := post.Tid
|
||||||
|
if _, ok := threadPostsMap[tid]; !ok {
|
||||||
|
threadPostsMap[tid] = []*Post{}
|
||||||
|
}
|
||||||
|
threadPostsMap[tid] = append(threadPostsMap[tid], post)
|
||||||
|
}
|
||||||
|
|
||||||
|
return threadPostsMap, len(posts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPostMapFromPosts(posts []*Post) map[int]*Post {
|
||||||
|
res := map[int]*Post{}
|
||||||
|
for _, post := range posts {
|
||||||
|
res[post.Pid] = post
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Thread struct {
|
||||||
|
Tid int
|
||||||
|
Fid int
|
||||||
|
Typeid int
|
||||||
|
Author string
|
||||||
|
Subject string
|
||||||
|
Dateline int
|
||||||
|
Lastpost int
|
||||||
|
Lastposter string
|
||||||
|
Views int
|
||||||
|
Replies int
|
||||||
|
Displayorder int
|
||||||
|
RecommendAdd int
|
||||||
|
RecommendSub int
|
||||||
|
Heats int
|
||||||
|
Favtimes int
|
||||||
|
|
||||||
|
Posts []*Post `xorm:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getThreads() []*Thread {
|
||||||
|
threads := []*Thread{}
|
||||||
|
err := adapter.Engine.Table("pre_forum_thread").Find(&threads)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return threads
|
||||||
|
}
|
||||||
|
|
||||||
|
func getThread(id int) *Thread {
|
||||||
|
thread := Thread{Tid: id}
|
||||||
|
existed, err := adapter.Engine.Table("pre_forum_thread").Get(&thread)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &thread
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getThreadMap() map[int]*Thread {
|
||||||
|
threads := getThreads()
|
||||||
|
|
||||||
|
m := map[int]*Thread{}
|
||||||
|
for _, thread := range threads {
|
||||||
|
m[thread.Tid] = thread
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func addThread(thread *Thread, threadPostsMap map[int][]*Post, attachments []*Attachment, forum *Forum, classMap map[int]*Class) (*object.Topic, []*object.Reply) {
|
||||||
|
posts := threadPostsMap[thread.Tid]
|
||||||
|
postMap := getPostMapFromPosts(posts)
|
||||||
|
|
||||||
|
thread.Posts = posts
|
||||||
|
|
||||||
|
// deleteWholeTopic(thread)
|
||||||
|
|
||||||
|
mutex := sync.RWMutex{}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(attachments))
|
||||||
|
for _, attachment := range attachments {
|
||||||
|
go func(attachment *Attachment) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
post := postMap[attachment.Pid]
|
||||||
|
if post != nil {
|
||||||
|
record := getRecordFromAttachment(attachment, post)
|
||||||
|
if record != nil {
|
||||||
|
mutex.Lock()
|
||||||
|
if post.UploadFileRecords == nil {
|
||||||
|
post.UploadFileRecords = []*object.UploadFileRecord{}
|
||||||
|
}
|
||||||
|
post.UploadFileRecords = append(post.UploadFileRecords, record)
|
||||||
|
mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}(attachment)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
topic, replies := getTopicAndReplies(thread, forum, classMap)
|
||||||
|
return topic, replies
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetThreads(t *testing.T) {
|
||||||
|
InitAdapter()
|
||||||
|
object.InitAdapter()
|
||||||
|
|
||||||
|
threadMap := getThreadMap()
|
||||||
|
for _, thread := range threadMap {
|
||||||
|
thread.Posts = []*Post{}
|
||||||
|
}
|
||||||
|
|
||||||
|
posts := getPosts()
|
||||||
|
for _, post := range posts {
|
||||||
|
if thread, ok := threadMap[post.Tid]; ok {
|
||||||
|
thread.Posts = append(thread.Posts, post)
|
||||||
|
} else {
|
||||||
|
// fmt.Printf("Failed to find thread: %d for post: %s\n", post.Tid, post.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// thread := threadMap[126152]
|
||||||
|
thread := threadMap[126239]
|
||||||
|
println(thread)
|
||||||
|
|
||||||
|
getTopicAndReplies(thread, nil, nil)
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/controllers"
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
AddThreadsConcurrency = 100
|
||||||
|
AddThreadsBatchSize = 10000
|
||||||
|
)
|
||||||
|
|
||||||
|
func addThreads(threads []*Thread, threadPostsMap map[int][]*Post, attachmentMap map[int][]*Attachment, forumMap map[int]*Forum, classMap map[int]*Class) {
|
||||||
|
arrayMutex := sync.RWMutex{}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(threads))
|
||||||
|
|
||||||
|
sem := make(chan int, AddThreadsConcurrency)
|
||||||
|
topics := []*object.Topic{}
|
||||||
|
replies := []*object.Reply{}
|
||||||
|
for i, thread := range threads {
|
||||||
|
sem <- 1
|
||||||
|
go func(i int, thread *Thread) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
attachments := attachmentMap[thread.Tid]
|
||||||
|
forum := forumMap[thread.Fid]
|
||||||
|
topic, replies2 := addThread(thread, threadPostsMap, attachments, forum, classMap)
|
||||||
|
if topic != nil && replies2 != nil {
|
||||||
|
arrayMutex.Lock()
|
||||||
|
topics = append(topics, topic)
|
||||||
|
replies = append(replies, replies2...)
|
||||||
|
arrayMutex.Unlock()
|
||||||
|
fmt.Printf("\t[%d/%d]: Added thread: tid = %d, fid = %d, replies = %d\n", i+1, len(threads), thread.Tid, thread.Fid, len(replies2))
|
||||||
|
} else {
|
||||||
|
fmt.Printf("\t[%d/%d]: Added thread: tid = %d, fid = %d, empty thread, removed\n", i+1, len(threads), thread.Tid, thread.Fid)
|
||||||
|
}
|
||||||
|
<-sem
|
||||||
|
}(i, thread)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
object.AddTopicsInBatch(topics)
|
||||||
|
object.AddRepliesInBatch(replies)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddThreads(t *testing.T) {
|
||||||
|
object.InitConfig()
|
||||||
|
InitAdapter()
|
||||||
|
object.InitAdapter()
|
||||||
|
controllers.InitAuthConfig()
|
||||||
|
|
||||||
|
attachmentMap := getAttachmentMap()
|
||||||
|
fmt.Printf("Loaded attachments: %d\n", len(attachmentMap))
|
||||||
|
_, forumMap := getForumTree()
|
||||||
|
fmt.Printf("Loaded forums: %d\n", len(forumMap))
|
||||||
|
classMap := getClassMap()
|
||||||
|
fmt.Printf("Loaded classes: %d\n", len(classMap))
|
||||||
|
threads := getThreads()
|
||||||
|
fmt.Printf("Loaded threads: %d\n", len(threads))
|
||||||
|
threadPostsMap, postCount := getThreadPostsMap()
|
||||||
|
fmt.Printf("Loaded posts: %d\n", postCount)
|
||||||
|
|
||||||
|
for i := 0; i < (len(threads)-1)/AddThreadsBatchSize+1; i++ {
|
||||||
|
start := i * AddThreadsBatchSize
|
||||||
|
end := (i + 1) * AddThreadsBatchSize
|
||||||
|
if end > len(threads) {
|
||||||
|
end = len(threads)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := threads[start:end]
|
||||||
|
fmt.Printf("Add threads: [%d - %d].\n", start, end)
|
||||||
|
addThreads(tmp, threadPostsMap, attachmentMap, forumMap, classMap)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,241 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
var MaxTopTime = "9999-00-00T00:00:00+08:00"
|
||||||
|
|
||||||
|
func getTopicFromThread(thread *Thread, forum *Forum, classMap map[int]*Class) *object.Topic {
|
||||||
|
content := ""
|
||||||
|
ip := ""
|
||||||
|
if thread.Posts[0].First == 1 {
|
||||||
|
content = thread.Posts[0].Message
|
||||||
|
ip = thread.Posts[0].Useip
|
||||||
|
} else {
|
||||||
|
panic("getTopicFromThread() error: thread.Posts[0].First != 1")
|
||||||
|
}
|
||||||
|
content = escapeContent(content)
|
||||||
|
content = addAttachmentsToContent(content, thread.Posts[0].UploadFileRecords)
|
||||||
|
|
||||||
|
tags := []string{}
|
||||||
|
if class, ok := classMap[thread.Typeid]; ok {
|
||||||
|
tags = append(tags, class.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
homePageTopTime := ""
|
||||||
|
tabTopTime := ""
|
||||||
|
nodeTopTime := ""
|
||||||
|
deleted := false
|
||||||
|
isHidden := false
|
||||||
|
state := ""
|
||||||
|
// https://blog.csdn.net/daily886/article/details/79569894
|
||||||
|
if thread.Displayorder == 3 {
|
||||||
|
homePageTopTime = MaxTopTime
|
||||||
|
} else if thread.Displayorder == 2 {
|
||||||
|
tabTopTime = MaxTopTime
|
||||||
|
} else if thread.Displayorder == 1 {
|
||||||
|
nodeTopTime = MaxTopTime
|
||||||
|
} else if thread.Displayorder == -1 {
|
||||||
|
deleted = true
|
||||||
|
} else if thread.Displayorder == -2 {
|
||||||
|
isHidden = true
|
||||||
|
state = "Reviewing"
|
||||||
|
} else if thread.Displayorder == -3 {
|
||||||
|
isHidden = true
|
||||||
|
state = "ReviewIgnored"
|
||||||
|
} else if thread.Displayorder == -4 {
|
||||||
|
isHidden = true
|
||||||
|
state = "Draft"
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeId := strconv.Itoa(thread.Fid)
|
||||||
|
tabId := ""
|
||||||
|
if forum != nil {
|
||||||
|
nodeId = forum.Name
|
||||||
|
|
||||||
|
if forum.Type == "group" || forum.Parent == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parentForum := forum.Parent
|
||||||
|
if parentForum.Parent != nil {
|
||||||
|
parentForum = parentForum.Parent
|
||||||
|
}
|
||||||
|
tabId = parentForum.Name
|
||||||
|
} else {
|
||||||
|
isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
topic := &object.Topic{
|
||||||
|
Id: thread.Tid,
|
||||||
|
Author: thread.Author,
|
||||||
|
NodeId: nodeId,
|
||||||
|
NodeName: nodeId,
|
||||||
|
TabId: tabId,
|
||||||
|
Title: thread.Subject,
|
||||||
|
CreatedTime: getTimeFromUnixSeconds(thread.Dateline),
|
||||||
|
Tags: tags,
|
||||||
|
LastReplyUser: thread.Lastposter,
|
||||||
|
LastReplyTime: getTimeFromUnixSeconds(thread.Lastpost),
|
||||||
|
ReplyCount: thread.Replies,
|
||||||
|
UpCount: thread.RecommendAdd,
|
||||||
|
DownCount: thread.RecommendSub,
|
||||||
|
HitCount: thread.Views,
|
||||||
|
Hot: thread.Heats,
|
||||||
|
FavoriteCount: thread.Favtimes,
|
||||||
|
HomePageTopTime: homePageTopTime,
|
||||||
|
TabTopTime: tabTopTime,
|
||||||
|
NodeTopTime: nodeTopTime,
|
||||||
|
Deleted: deleted,
|
||||||
|
Content: content,
|
||||||
|
IsHidden: isHidden,
|
||||||
|
Ip: ip,
|
||||||
|
State: state,
|
||||||
|
}
|
||||||
|
return topic
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReplyFromPost(topicId int, post *Post) *object.Reply {
|
||||||
|
content := escapeContent(post.Message)
|
||||||
|
content = addAttachmentsToContent(content, post.UploadFileRecords)
|
||||||
|
|
||||||
|
deleted := false
|
||||||
|
isHidden := false
|
||||||
|
state := ""
|
||||||
|
// https://blog.csdn.net/fengda2870/article/details/8699229
|
||||||
|
if post.Invisible == -2 {
|
||||||
|
isHidden = true
|
||||||
|
state = "Reviewing"
|
||||||
|
} else if post.Invisible == -3 {
|
||||||
|
isHidden = true
|
||||||
|
state = "ReviewIgnored"
|
||||||
|
} else if post.Invisible == -5 {
|
||||||
|
deleted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
reply := &object.Reply{
|
||||||
|
Id: post.Pid,
|
||||||
|
Author: post.Author,
|
||||||
|
TopicId: topicId,
|
||||||
|
CreatedTime: getTimeFromUnixSeconds(post.Dateline),
|
||||||
|
Deleted: deleted,
|
||||||
|
IsHidden: isHidden,
|
||||||
|
ThanksNum: 0,
|
||||||
|
Content: content,
|
||||||
|
Ip: post.Useip,
|
||||||
|
State: state,
|
||||||
|
}
|
||||||
|
return reply
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteWholeTopic(thread *Thread) {
|
||||||
|
topics := object.GetTopicsByTitleAndAuthor(thread.Subject, thread.Author)
|
||||||
|
for _, topic := range topics {
|
||||||
|
topicId := topic.Id
|
||||||
|
object.DeleteTopicHard(topicId)
|
||||||
|
object.DeleteFilesByMember(thread.Author)
|
||||||
|
|
||||||
|
//replies := object.GetReplies(topicId, "")
|
||||||
|
//for _, reply := range replies {
|
||||||
|
// object.DeleteFilesByMember(reply.Author)
|
||||||
|
//}
|
||||||
|
object.DeleteRepliesHardByTopicId(topicId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func recordToHyperlink(record *object.UploadFileRecord) string {
|
||||||
|
if record.FileType == "image" {
|
||||||
|
return fmt.Sprintf("\n", record.FileName, record.FileUrl)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("- [%s](%s)\n", record.FileName, record.FileUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addAttachmentsToContent(content string, records []*object.UploadFileRecord) string {
|
||||||
|
images := []*object.UploadFileRecord{}
|
||||||
|
files := []*object.UploadFileRecord{}
|
||||||
|
|
||||||
|
for _, record := range records {
|
||||||
|
if record.FileType == "image" {
|
||||||
|
images = append(images, record)
|
||||||
|
} else {
|
||||||
|
files = append(files, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(images) != 0 {
|
||||||
|
content += "\n\n### 图片:\n\n"
|
||||||
|
for _, record := range images {
|
||||||
|
content += recordToHyperlink(record)
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(files) != 0 {
|
||||||
|
content += "\n\n### 附件:\n\n"
|
||||||
|
for _, record := range files {
|
||||||
|
content += recordToHyperlink(record)
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTopicAndReplies(thread *Thread, forum *Forum, classMap map[int]*Class) (*object.Topic, []*object.Reply) {
|
||||||
|
// remove leading useless posts
|
||||||
|
posts := []*Post{}
|
||||||
|
isBeforeFirstPosition := true
|
||||||
|
for _, post := range thread.Posts {
|
||||||
|
if !isBeforeFirstPosition || post.First == 1 {
|
||||||
|
isBeforeFirstPosition = false
|
||||||
|
posts = append(posts, post)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thread.Posts = posts
|
||||||
|
|
||||||
|
if len(thread.Posts) == 0 {
|
||||||
|
// thread is deleted.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
topic := getTopicFromThread(thread, forum, classMap)
|
||||||
|
if topic == nil {
|
||||||
|
// thread doesn't belong to any forum, ignore it
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
replies := []*object.Reply{}
|
||||||
|
for i, post := range thread.Posts {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//if post.First == 1 {
|
||||||
|
// panic(fmt.Errorf("getTopicAndReplies() error: thread.Posts[%d].First == 1", i))
|
||||||
|
//}
|
||||||
|
|
||||||
|
reply := getReplyFromPost(thread.Tid, post)
|
||||||
|
replies = append(replies, reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
return topic, replies
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getUserFromMember(memberEx *MemberEx) *casdoorsdk.User {
|
||||||
|
user := &casdoorsdk.User{
|
||||||
|
Owner: CasdoorOrganization,
|
||||||
|
Name: memberEx.Member.Username,
|
||||||
|
CreatedTime: getTimeFromUnixSeconds(memberEx.Member.Regdate),
|
||||||
|
Id: strconv.Itoa(memberEx.Member.Uid),
|
||||||
|
Type: "normal-user",
|
||||||
|
// Password: memberEx.UcenterMember.Password,
|
||||||
|
// PasswordSalt: memberEx.UcenterMember.Salt,
|
||||||
|
// DisplayName: displayName,
|
||||||
|
Avatar: "",
|
||||||
|
PermanentAvatar: "*",
|
||||||
|
Email: memberEx.Member.Email,
|
||||||
|
// Phone: memberEx.Profile.Mobile,
|
||||||
|
// Location: memberEx.Profile.Residecity,
|
||||||
|
Address: []string{},
|
||||||
|
// Affiliation: memberEx.Profile.Occupation,
|
||||||
|
// Title: memberEx.Profile.Position,
|
||||||
|
// IdCardType: idCardType,
|
||||||
|
// IdCard: idCard,
|
||||||
|
// Homepage: memberEx.Profile.Site,
|
||||||
|
// Bio: memberEx.Profile.Bio,
|
||||||
|
// Tag: memberEx.Profile.Interest,
|
||||||
|
Region: "CN",
|
||||||
|
Language: "zh",
|
||||||
|
// Gender: gender,
|
||||||
|
// Birthday: birthday,
|
||||||
|
// Education: memberEx.Profile.Education,
|
||||||
|
Score: memberEx.Member.Credits,
|
||||||
|
Ranking: memberEx.Member.Uid,
|
||||||
|
IsOnline: false,
|
||||||
|
IsAdmin: false,
|
||||||
|
IsGlobalAdmin: false,
|
||||||
|
IsForbidden: false,
|
||||||
|
IsDeleted: false,
|
||||||
|
SignupApplication: CasdoorApplication,
|
||||||
|
// CreatedIp: memberEx.UcenterMember.Regip,
|
||||||
|
// LastSigninTime: getTimeFromUnixSeconds(memberEx.UcenterMember.Lastlogintime),
|
||||||
|
LastSigninIp: "",
|
||||||
|
Properties: map[string]string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if memberEx.UcenterMember == nil {
|
||||||
|
fmt.Printf("[%d, %s] memberEx.UcenterMember == nil\n", memberEx.Member.Uid, memberEx.Member.Username)
|
||||||
|
} else {
|
||||||
|
user.Password = memberEx.UcenterMember.Password
|
||||||
|
user.PasswordSalt = memberEx.UcenterMember.Salt
|
||||||
|
user.CreatedIp = memberEx.UcenterMember.Regip
|
||||||
|
user.LastSigninTime = getTimeFromUnixSeconds(memberEx.UcenterMember.Lastlogintime)
|
||||||
|
}
|
||||||
|
|
||||||
|
if memberEx.Profile == nil {
|
||||||
|
fmt.Printf("[%d, %s] memberEx.Profile == nil\n", memberEx.Member.Uid, memberEx.Member.Username)
|
||||||
|
} else {
|
||||||
|
displayName := memberEx.Profile.Realname
|
||||||
|
if displayName == "" {
|
||||||
|
displayName = memberEx.Member.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
idCardType := ""
|
||||||
|
idCard := ""
|
||||||
|
if memberEx.Profile.Idcard != "" {
|
||||||
|
idCardType = "IdCard"
|
||||||
|
idCard = memberEx.Profile.Idcard
|
||||||
|
}
|
||||||
|
|
||||||
|
gender := "Male"
|
||||||
|
if memberEx.Profile.Gender == 2 {
|
||||||
|
gender = "Female"
|
||||||
|
}
|
||||||
|
|
||||||
|
birthday := ""
|
||||||
|
if memberEx.Profile.Birthyear != 0 && memberEx.Profile.Birthmonth != 0 && memberEx.Profile.Birthday != 0 {
|
||||||
|
birthday = fmt.Sprintf("%02d-%02d-%02d", memberEx.Profile.Birthyear, memberEx.Profile.Birthmonth, memberEx.Profile.Birthday)
|
||||||
|
}
|
||||||
|
|
||||||
|
address := []string{}
|
||||||
|
if memberEx.Profile.Resideprovince != "" {
|
||||||
|
address = append(address, memberEx.Profile.Resideprovince)
|
||||||
|
if memberEx.Profile.Residecity != "" {
|
||||||
|
address = append(address, memberEx.Profile.Residecity)
|
||||||
|
if memberEx.Profile.Residedist != "" {
|
||||||
|
address = append(address, memberEx.Profile.Residedist)
|
||||||
|
if memberEx.Profile.Residecommunity != "" {
|
||||||
|
address = append(address, memberEx.Profile.Residecommunity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
user.Address = address
|
||||||
|
|
||||||
|
user.DisplayName = displayName
|
||||||
|
user.IdCardType = idCardType
|
||||||
|
user.IdCard = idCard
|
||||||
|
user.Gender = gender
|
||||||
|
user.Birthday = birthday
|
||||||
|
|
||||||
|
user.Phone = memberEx.Profile.Mobile
|
||||||
|
user.Location = memberEx.Profile.Residecity
|
||||||
|
user.Affiliation = memberEx.Profile.Occupation
|
||||||
|
user.Title = memberEx.Profile.Position
|
||||||
|
user.Homepage = memberEx.Profile.Site
|
||||||
|
user.Bio = memberEx.Profile.Bio
|
||||||
|
user.Tag = memberEx.Profile.Interest
|
||||||
|
user.Education = memberEx.Profile.Education
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/casdoor"
|
||||||
|
"github.com/casbin/casnode/controllers"
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
var AddUsersConcurrency = 20
|
||||||
|
|
||||||
|
func TestAddUsers(t *testing.T) {
|
||||||
|
object.InitConfig()
|
||||||
|
InitAdapter()
|
||||||
|
object.InitAdapter()
|
||||||
|
casdoor.InitCasdoorAdapter()
|
||||||
|
controllers.InitAuthConfig()
|
||||||
|
|
||||||
|
membersEx := getMembersEx()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(membersEx))
|
||||||
|
|
||||||
|
sem := make(chan int, AddUsersConcurrency)
|
||||||
|
users := []*casdoorsdk.User{}
|
||||||
|
for i, memberEx := range membersEx {
|
||||||
|
sem <- 1
|
||||||
|
go func(i int, memberEx *MemberEx) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
user := getUserFromMember(memberEx)
|
||||||
|
users = append(users, user)
|
||||||
|
fmt.Printf("[%d/%d]: Added user: [%d, %s]\n", i+1, len(membersEx), memberEx.Member.Uid, memberEx.Member.Username)
|
||||||
|
<-sem
|
||||||
|
}(i, memberEx)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
casdoor.AddUsersInBatch(users)
|
||||||
|
}
|
|
@ -0,0 +1,180 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package discuzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
reBold *regexp.Regexp
|
||||||
|
reAlign *regexp.Regexp
|
||||||
|
reFont *regexp.Regexp
|
||||||
|
reUrl *regexp.Regexp
|
||||||
|
reSize *regexp.Regexp
|
||||||
|
reSize2 *regexp.Regexp
|
||||||
|
reSize3 *regexp.Regexp
|
||||||
|
reVideo *regexp.Regexp
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
reBold, _ = regexp.Compile("\\[b](.*?)\\[/b]")
|
||||||
|
reAlign, _ = regexp.Compile("\\[align=([a-z]+)](.*?)\\[/align]")
|
||||||
|
reFont, _ = regexp.Compile("\\[font=([^]]+)](.*?)\\[/font]")
|
||||||
|
reUrl, _ = regexp.Compile("\\[url=([^]]+)](.*?)\\[/url]")
|
||||||
|
reSize, _ = regexp.Compile("\\[[a-z]+(=[^]]+)?]")
|
||||||
|
reSize2, _ = regexp.Compile("\\[/align]")
|
||||||
|
reSize3, _ = regexp.Compile("\\[/[a-z]+]")
|
||||||
|
// reSize, _ = regexp.Compile("\\[size=\\d+\\].*\\[/size\\]")
|
||||||
|
reVideo, _ = regexp.Compile("\\[media=x,(\\d+),(\\d+)\\].*/id_(.*)\\.html\\[/media\\]")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTimeFromUnixSeconds(t int) string {
|
||||||
|
tm := time.Unix(int64(t), 0)
|
||||||
|
return tm.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getYearFromUnixSeconds(t int) int {
|
||||||
|
tm := time.Unix(int64(t), 0)
|
||||||
|
return tm.Year()
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapeVideo(text string) string {
|
||||||
|
// [media=x,500,375]https://v.youku.com/v_show/id_XNDU0NjEyODg0MA==.html[/media]
|
||||||
|
// <iframe height=498 width=510 src='https://player.youku.com/embed/XNDU0NjEyODg0MA==' frameborder=0 'allowfullscreen'></iframe>
|
||||||
|
text = reVideo.ReplaceAllString(text, "\n<iframe width=$1 height=$2 src='https://player.youku.com/embed/$3' frameborder=0 'allowfullscreen'></iframe>\n")
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapeContent(text string) string {
|
||||||
|
text = strings.ReplaceAll(text, "[quote]", "```\n")
|
||||||
|
text = strings.ReplaceAll(text, "[/quote]", "\n```")
|
||||||
|
text = reBold.ReplaceAllString(text, "<b>$1</b>")
|
||||||
|
text = reAlign.ReplaceAllString(text, "<p align=\"$1\">$2</p>")
|
||||||
|
text = reFont.ReplaceAllString(text, "<font face=\"$1\">$2</font>")
|
||||||
|
text = reUrl.ReplaceAllString(text, "[$2]($1)")
|
||||||
|
text = reSize.ReplaceAllString(text, "")
|
||||||
|
text = reSize2.ReplaceAllString(text, "\n")
|
||||||
|
text = reSize3.ReplaceAllString(text, "")
|
||||||
|
text = escapeVideo(text)
|
||||||
|
text = strings.ReplaceAll(text, "\n", "\n\n")
|
||||||
|
text = strings.ReplaceAll(text, "\r", "")
|
||||||
|
text = strings.ReplaceAll(text, "\n\n\n", "\n\n<br />\n\n")
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRedirectUrl(url string) string {
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newUrl := ""
|
||||||
|
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||||
|
newUrl = req.URL.String()
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
times := 0
|
||||||
|
for {
|
||||||
|
_, err = client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
times += 1
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
if times >= 10 {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if newUrl == "" {
|
||||||
|
newUrl = url
|
||||||
|
}
|
||||||
|
|
||||||
|
return newUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadFile(url string) ([]byte, string, error) {
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
err := Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(resp.Body)
|
||||||
|
|
||||||
|
bs, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
newUrl := resp.Request.URL.String()
|
||||||
|
return bs, newUrl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadFileSafe(url string) ([]byte, string, error) {
|
||||||
|
var bs []byte
|
||||||
|
var newUrl string
|
||||||
|
var err error
|
||||||
|
times := 0
|
||||||
|
for {
|
||||||
|
bs, newUrl, err = downloadFile(url)
|
||||||
|
if err != nil {
|
||||||
|
times += 1
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
if times >= 10 {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bs, newUrl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStringHash(s string) int {
|
||||||
|
h := fnv.New32a()
|
||||||
|
h.Write([]byte(s))
|
||||||
|
return int(h.Sum32())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRandomAvatarUrl(s string) string {
|
||||||
|
i := getStringHash(s)
|
||||||
|
i = i%244 + 1
|
||||||
|
avatarUrl := fmt.Sprintf("%s%d.png", avatarPoolBaseUrl, i)
|
||||||
|
return avatarUrl
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
version: '3.1'
|
||||||
|
services:
|
||||||
|
casnode:
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "7000:7000"
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
volumes:
|
||||||
|
- ./conf:/conf/
|
||||||
|
db:
|
||||||
|
restart: always
|
||||||
|
image: mysql:8.0.25
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: 123
|
||||||
|
volumes:
|
||||||
|
- /usr/local/docker/mysqls:/var/lib/mysql
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
module github.com/casbin/casnode
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect
|
||||||
|
github.com/astaxie/beego v1.12.3
|
||||||
|
github.com/casbin/google-groups-crawler v0.1.3
|
||||||
|
github.com/casdoor/casdoor-go-sdk v0.10.0
|
||||||
|
github.com/chromedp/chromedp v0.8.4
|
||||||
|
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.5.0
|
||||||
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd
|
||||||
|
github.com/huichen/sego v0.0.0-20180617034105-3f3c8a8cfacc
|
||||||
|
github.com/issue9/assert v1.4.1
|
||||||
|
github.com/lib/pq v1.10.2 // indirect
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.5
|
||||||
|
github.com/mileusna/crontab v1.0.1
|
||||||
|
github.com/mozillazg/go-slugify v0.2.0
|
||||||
|
github.com/mozillazg/go-unidecode v0.1.1 // indirect
|
||||||
|
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d // indirect
|
||||||
|
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
|
||||||
|
github.com/prometheus/client_golang v1.11.0 // indirect
|
||||||
|
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
|
||||||
|
github.com/smartystreets/goconvey v1.7.2 // indirect
|
||||||
|
github.com/sromku/go-gitter v0.0.0-20170828210750-70f7030a94a6
|
||||||
|
github.com/stretchr/testify v1.7.0 // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
|
||||||
|
golang.org/x/text v0.3.5 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
|
xorm.io/core v0.7.2
|
||||||
|
xorm.io/xorm v0.8.1
|
||||||
|
)
|
|
@ -0,0 +1,650 @@
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
|
||||||
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||||
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
|
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||||
|
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||||
|
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||||
|
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||||
|
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||||
|
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||||
|
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||||
|
cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8=
|
||||||
|
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||||
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
|
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||||
|
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||||
|
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||||
|
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||||
|
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||||
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
|
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||||
|
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||||
|
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||||
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
|
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||||
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
|
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||||
|
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||||
|
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d h1:ir/IFJU5xbja5UaBEQLjcvn7aAU01nqU/NUyOBEU+ew=
|
||||||
|
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d/go.mod h1:PRWNwWq0yifz6XDPZu48aSld8BWwBfr2JKB2bGWiEd4=
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
|
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
|
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
|
||||||
|
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
|
github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
|
||||||
|
github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
|
||||||
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||||
|
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
|
||||||
|
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
||||||
|
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
|
||||||
|
github.com/casbin/google-groups-crawler v0.1.3 h1:kmbzjLK88dtSTk7ycDvjKH6hwVB0z6dAJGpJvvqRFsg=
|
||||||
|
github.com/casbin/google-groups-crawler v0.1.3/go.mod h1:JHKvWP8blOe/Mbob3R4aaU5RvVIOC83eBcCSlKsbKSI=
|
||||||
|
github.com/casdoor/casdoor-go-sdk v0.10.0 h1:gakpRFtGgesKhNb002C2bf66cUb+1uZzj8eEaRKpcJU=
|
||||||
|
github.com/casdoor/casdoor-go-sdk v0.10.0/go.mod h1:MBed3ISHQfXTtoOCAk5T8l5lt4wFvsyynrw0awggydY=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
|
||||||
|
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
|
||||||
|
github.com/chromedp/cdproto v0.0.0-20220812200530-d0d83820bffc h1:xGhCpiX5oNMoZGnzYvv1ne4muVRl2SDHH5fL7oUbZAY=
|
||||||
|
github.com/chromedp/cdproto v0.0.0-20220812200530-d0d83820bffc/go.mod h1:5Y4sD/eXpwrChIuxhSr/G20n9CdbCmoerOHnuAf0Zr0=
|
||||||
|
github.com/chromedp/chromedp v0.8.4 h1:50NSLCXC38kGsCmLCi8tXZ5V5FtQ3BOV/90u1O06Gq4=
|
||||||
|
github.com/chromedp/chromedp v0.8.4/go.mod h1:ikuiJrEMoOnTPFUBu6jV0XZFOGW6W/Nhgk/OZyvh00o=
|
||||||
|
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
||||||
|
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
|
||||||
|
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
|
||||||
|
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
|
||||||
|
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADGYw5LqMnHqSkyIELsHCGF6PkrmM31V8rF7o=
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
|
||||||
|
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||||
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||||
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
|
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||||
|
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
|
||||||
|
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||||
|
github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
|
||||||
|
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||||
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
|
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
|
||||||
|
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
|
||||||
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
|
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
|
||||||
|
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
|
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd h1:0b8AqsWQb6A0jjx80UXLG/uMTXQkGD0IGuXWqsrNz1M=
|
||||||
|
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
|
||||||
|
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
||||||
|
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
|
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||||
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/huichen/sego v0.0.0-20180617034105-3f3c8a8cfacc h1:3LXYtoxQGFSjIL5ZJAn4PceSpwRohuTKYL1W4kJ7G8g=
|
||||||
|
github.com/huichen/sego v0.0.0-20180617034105-3f3c8a8cfacc/go.mod h1:+/Bm7uk1bnJJMi9l6P88FgHeGtscOQiYbxW1j+BmgBY=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/issue9/assert v1.4.1 h1:gUtOpMTeaE4JTe9kACma5foOHBvVt1p5XTFrULDwdXI=
|
||||||
|
github.com/issue9/assert v1.4.1/go.mod h1:Yktk83hAVl1SPSYtd9kjhBizuiBIqUQyj+D5SE2yjVY=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||||
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
|
||||||
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||||
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||||
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||||
|
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.5 h1:cF59UCKMmmUgqN1baLvqU/B1ZsMori+duLVTLpgiG3w=
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.5/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
|
||||||
|
github.com/mileusna/crontab v1.0.1 h1:YrDLc7l3xOiznmXq2FtAgg+1YQ3yC6pfFVPe+ywXNtg=
|
||||||
|
github.com/mileusna/crontab v1.0.1/go.mod h1:dbns64w/u3tUnGZGf8pAa76ZqOfeBX4olW4U1ZwExmc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/mozillazg/go-slugify v0.2.0 h1:SIhqDlnJWZH8OdiTmQgeXR28AOnypmAXPeOTcG7b9lk=
|
||||||
|
github.com/mozillazg/go-slugify v0.2.0/go.mod h1:z7dPH74PZf2ZPFkyxx+zjPD8CNzRJNa1CGacv0gg8Ns=
|
||||||
|
github.com/mozillazg/go-unidecode v0.1.1 h1:uiRy1s4TUqLbcROUrnCN/V85Jlli2AmDF6EeAXOeMHE=
|
||||||
|
github.com/mozillazg/go-unidecode v0.1.1/go.mod h1:fYMdhyjni9ZeEmS6OE/GJHDLsF8TQvIVDwYR/drR26Q=
|
||||||
|
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d h1:tLWCMSjfL8XyZwpu1RzI2UpJSPbZCOZ6DVHQFnlpL7A=
|
||||||
|
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
||||||
|
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff h1:HLGD5/9UxxfEuO9DtP8gnTmNtMxbPyhYltfxsITel8g=
|
||||||
|
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff/go.mod h1:B8jLfIIPn2sKyWr0D7cL2v7tnrDD5z291s2Zypdu89E=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
|
||||||
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
|
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||||
|
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
||||||
|
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||||
|
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
|
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
|
||||||
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||||
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
|
github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
|
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
|
||||||
|
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||||
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||||
|
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
|
||||||
|
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
|
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
||||||
|
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
|
||||||
|
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik=
|
||||||
|
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
|
||||||
|
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
|
||||||
|
github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
|
||||||
|
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
|
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
||||||
|
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||||
|
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
||||||
|
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
|
||||||
|
github.com/sromku/go-gitter v0.0.0-20170828210750-70f7030a94a6 h1:7AV47xvYbuwoNxR9LDhkRwqzZsySCX5H8WVM4zrDmME=
|
||||||
|
github.com/sromku/go-gitter v0.0.0-20170828210750-70f7030a94a6/go.mod h1:P2BoF5QlNE1UcKtYKP8xa8B9I5eALYU5JpRdCqLddL4=
|
||||||
|
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||||
|
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||||
|
github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||||
|
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
|
||||||
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
|
||||||
|
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||||
|
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||||
|
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||||
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
|
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||||
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||||
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
|
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||||
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||||
|
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
|
||||||
|
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||||
|
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||||
|
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
|
xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8=
|
||||||
|
xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU=
|
||||||
|
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
|
||||||
|
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
|
||||||
|
xorm.io/xorm v0.8.1 h1:4f2KXuQxVdaX3RdI3Fw81NzMiSpZeyCZt8m3sEVeIkQ=
|
||||||
|
xorm.io/xorm v0.8.1/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY=
|
|
@ -0,0 +1,97 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package i18n
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type I18nData map[string]map[string]string
|
||||||
|
|
||||||
|
var reI18n *regexp.Regexp
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
reI18n, _ = regexp.Compile("i18next.t\\(\"(.*?)\"\\)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAllI18nStrings(fileContent string) []string {
|
||||||
|
res := []string{}
|
||||||
|
|
||||||
|
matches := reI18n.FindAllStringSubmatch(fileContent, -1)
|
||||||
|
if matches == nil {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, match := range matches {
|
||||||
|
res = append(res, match[1])
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAllJsFilePaths() []string {
|
||||||
|
path := "../web/src"
|
||||||
|
|
||||||
|
res := []string{}
|
||||||
|
err := filepath.Walk(path,
|
||||||
|
func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(info.Name(), ".js") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, path)
|
||||||
|
fmt.Println(path, info.Name())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseToData() *I18nData {
|
||||||
|
allWords := []string{}
|
||||||
|
paths := getAllJsFilePaths()
|
||||||
|
for _, path := range paths {
|
||||||
|
fileContent := util.ReadStringFromPath(path)
|
||||||
|
words := getAllI18nStrings(fileContent)
|
||||||
|
allWords = append(allWords, words...)
|
||||||
|
}
|
||||||
|
fmt.Printf("%v\n", allWords)
|
||||||
|
|
||||||
|
data := I18nData{}
|
||||||
|
for _, word := range allWords {
|
||||||
|
tokens := strings.Split(word, ":")
|
||||||
|
namespace := tokens[0]
|
||||||
|
key := tokens[1]
|
||||||
|
|
||||||
|
if _, ok := data[namespace]; !ok {
|
||||||
|
data[namespace] = map[string]string{}
|
||||||
|
}
|
||||||
|
data[namespace][key] = key
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package i18n
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func applyToOtherLanguage(dataEn *I18nData, lang string) {
|
||||||
|
dataOther := readI18nFile(lang)
|
||||||
|
println(dataOther)
|
||||||
|
|
||||||
|
applyData(dataEn, dataOther)
|
||||||
|
writeI18nFile(lang, dataEn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateI18nStrings(t *testing.T) {
|
||||||
|
dataEn := parseToData()
|
||||||
|
writeI18nFile("en", dataEn)
|
||||||
|
|
||||||
|
applyToOtherLanguage(dataEn, "de")
|
||||||
|
applyToOtherLanguage(dataEn, "fr")
|
||||||
|
applyToOtherLanguage(dataEn, "ja")
|
||||||
|
applyToOtherLanguage(dataEn, "ko")
|
||||||
|
applyToOtherLanguage(dataEn, "ru")
|
||||||
|
applyToOtherLanguage(dataEn, "zh")
|
||||||
|
applyToOtherLanguage(dataEn, "zh-TW")
|
||||||
|
applyToOtherLanguage(dataEn, "kk")
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package i18n
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getI18nFilePath(language string) string {
|
||||||
|
return fmt.Sprintf("../web/src/locales/%s/data.json", language)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readI18nFile(language string) *I18nData {
|
||||||
|
s := util.ReadStringFromPath(getI18nFilePath(language))
|
||||||
|
|
||||||
|
data := &I18nData{}
|
||||||
|
err := util.JsonToStruct(s, data)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeI18nFile(language string, data *I18nData) {
|
||||||
|
s := util.StructToJsonFormatted(data)
|
||||||
|
s = s + "\n"
|
||||||
|
println(s)
|
||||||
|
|
||||||
|
util.WriteStringToPath(s, getI18nFilePath(language))
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyData(data1 *I18nData, data2 *I18nData) {
|
||||||
|
for namespace, pairs2 := range *data2 {
|
||||||
|
if _, ok := (*data1)[namespace]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs1 := (*data1)[namespace]
|
||||||
|
|
||||||
|
for key, value := range pairs2 {
|
||||||
|
if _, ok := pairs1[key]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs1[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/astaxie/beego/plugins/cors"
|
||||||
|
_ "github.com/astaxie/beego/session/redis"
|
||||||
|
"github.com/casbin/casnode/casdoor"
|
||||||
|
"github.com/casbin/casnode/object"
|
||||||
|
"github.com/casbin/casnode/routers"
|
||||||
|
"github.com/casbin/casnode/service"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
object.InitAdapter()
|
||||||
|
object.InitHttpClient()
|
||||||
|
casdoor.InitCasdoorAdapter()
|
||||||
|
service.InitDictionary()
|
||||||
|
util.InitSegmenter()
|
||||||
|
object.InitForumBasicInfo()
|
||||||
|
object.InitFrontConf()
|
||||||
|
object.InitTimer()
|
||||||
|
|
||||||
|
beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
|
||||||
|
AllowOrigins: []string{"*"},
|
||||||
|
AllowMethods: []string{"GET", "PUT", "PATCH"},
|
||||||
|
AllowHeaders: []string{"Origin"},
|
||||||
|
ExposeHeaders: []string{"Content-Length"},
|
||||||
|
AllowCredentials: true,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// beego.DelStaticPath("/static")
|
||||||
|
beego.SetStaticPath("/static", "web/build/static")
|
||||||
|
beego.SetStaticPath("/swagger", "swagger")
|
||||||
|
beego.BConfig.WebConfig.DirectoryIndex = true
|
||||||
|
|
||||||
|
// https://studygolang.com/articles/2303
|
||||||
|
beego.InsertFilter("*", beego.BeforeRouter, routers.BotFilter)
|
||||||
|
beego.InsertFilter("*", beego.BeforeRouter, routers.Static)
|
||||||
|
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
|
||||||
|
|
||||||
|
if beego.AppConfig.String("redisEndpoint") == "" {
|
||||||
|
beego.BConfig.WebConfig.Session.SessionProvider = "file"
|
||||||
|
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
|
||||||
|
} else {
|
||||||
|
beego.BConfig.WebConfig.Session.SessionProvider = "redis"
|
||||||
|
beego.BConfig.WebConfig.Session.SessionProviderConfig = beego.AppConfig.String("redisEndpoint")
|
||||||
|
}
|
||||||
|
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 30
|
||||||
|
|
||||||
|
beego.Run()
|
||||||
|
}
|
|
@ -0,0 +1,196 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
adapter *Adapter
|
||||||
|
CasdoorOrganization string
|
||||||
|
CasdoorApplication string
|
||||||
|
)
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
SessionKey string `xorm:"char(64) notnull pk"`
|
||||||
|
SessionData []uint8 `xorm:"blob"`
|
||||||
|
SessionExpiry int `xorm:"notnull"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitConfig() {
|
||||||
|
err := beego.LoadAppConfig("ini", "../conf/app.conf")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitAdapter() {
|
||||||
|
adapter = NewAdapter(beego.AppConfig.String("driverName"), beego.AppConfig.String("dataSourceName"), beego.AppConfig.String("dbName"))
|
||||||
|
adapter.createTable()
|
||||||
|
|
||||||
|
CasdoorOrganization = beego.AppConfig.String("casdoorOrganization")
|
||||||
|
CasdoorApplication = beego.AppConfig.String("casdoorApplication")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapter represents the MySQL adapter for policy storage.
|
||||||
|
type Adapter struct {
|
||||||
|
driverName string
|
||||||
|
dataSourceName string
|
||||||
|
dbName string
|
||||||
|
Engine *xorm.Engine
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalizer is the destructor for Adapter.
|
||||||
|
func finalizer(a *Adapter) {
|
||||||
|
err := a.Engine.Close()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAdapter is the constructor for Adapter.
|
||||||
|
func NewAdapter(driverName string, dataSourceName string, dbName string) *Adapter {
|
||||||
|
a := &Adapter{}
|
||||||
|
a.driverName = driverName
|
||||||
|
a.dataSourceName = dataSourceName
|
||||||
|
a.dbName = dbName
|
||||||
|
|
||||||
|
// Open the DB, create it if not existed.
|
||||||
|
a.open()
|
||||||
|
|
||||||
|
// Call the destructor when the object is released.
|
||||||
|
runtime.SetFinalizer(a, finalizer)
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) createDatabase() error {
|
||||||
|
Engine, err := xorm.NewEngine(a.driverName, a.dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer Engine.Close()
|
||||||
|
|
||||||
|
_, err = Engine.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s default charset utf8 COLLATE utf8_general_ci", a.dbName))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) open() {
|
||||||
|
if a.driverName != "postgres" {
|
||||||
|
if err := a.createDatabase(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine, err := xorm.NewEngine(a.driverName, a.dataSourceName+a.dbName)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Engine = Engine
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) close() {
|
||||||
|
a.Engine.Close()
|
||||||
|
a.Engine = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) createTable() {
|
||||||
|
err := a.Engine.Sync2(new(Session))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Topic))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Reply))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Poster))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Translator))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Node))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Favorites))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Tab))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Notification))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(BasicInfo))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Plane))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(ConsumptionRecord))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(BrowseRecord))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(UploadFileRecord))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(SensitiveWord))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(FrontConf))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
)
|
||||||
|
|
||||||
|
var CasdoorStorageEndpoint = beego.AppConfig.String("casdoorStorageEndpoint")
|
||||||
|
|
||||||
|
func getUserAvatar(username string) string {
|
||||||
|
return fmt.Sprintf("%scasdoor/avatar/%s/%s.png", CasdoorStorageEndpoint, CasdoorOrganization, username)
|
||||||
|
}
|
|
@ -0,0 +1,263 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConsumptionType 1-9 means:
|
||||||
|
// login bonus, receive thanks(topic), receive thanks(reply), thanks(topic)
|
||||||
|
// thanks(reply), new reply, receive reply bonus, new topic, top topic.
|
||||||
|
type ConsumptionRecord struct {
|
||||||
|
Id int `xorm:"int notnull pk autoincr" json:"id"`
|
||||||
|
Amount int `xorm:"int" json:"amount"`
|
||||||
|
Balance int `xorm:"int" json:"balance"`
|
||||||
|
ConsumerId string `xorm:"varchar(100) index" json:"consumerId"`
|
||||||
|
ObjectId int `xorm:"int index" json:"objectId"`
|
||||||
|
ReceiverId string `xorm:"varchar(100) index" json:"receiverId"`
|
||||||
|
CreatedTime string `xorm:"varchar(40)" json:"createdTime"`
|
||||||
|
ConsumptionType int `xorm:"int" json:"consumptionType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBalances() []*ConsumptionRecord {
|
||||||
|
balances := []*ConsumptionRecord{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Find(&balances)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return balances
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMemberBalances(id string, limit, offset int) []*ConsumptionRecord {
|
||||||
|
balances := []*ConsumptionRecord{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Where("receiver_id = ?", id).Find(&balances)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return balances
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddBalance(balance *ConsumptionRecord) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(balance)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMemberBalance(user *casdoorsdk.User) int {
|
||||||
|
return user.Score
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateMemberBalance(user *casdoorsdk.User, amount int) (bool, error) {
|
||||||
|
user.Score += amount
|
||||||
|
return casdoorsdk.UpdateUserForColumns(user, []string{"score"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateMemberConsumptionSum(user *casdoorsdk.User, amount int) (bool, error) {
|
||||||
|
user.Karma += amount
|
||||||
|
return casdoorsdk.UpdateUserForColumns(user, []string{"karma"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMemberConsumptionRecordNum(memberId string) int {
|
||||||
|
var total int64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
record := new(ConsumptionRecord)
|
||||||
|
total, err = adapter.Engine.Where("receiver_id = ?", memberId).Count(record)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMemberConsumptionRecord(id string, limit, offset int) []*BalanceResponse {
|
||||||
|
record := GetMemberBalances(id, limit, offset)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
errChan := make(chan error, limit+1)
|
||||||
|
res := make([]*BalanceResponse, len(record))
|
||||||
|
for k, v := range record {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(k int, v *ConsumptionRecord) {
|
||||||
|
defer wg.Done()
|
||||||
|
tempRecord := BalanceResponse{
|
||||||
|
Amount: v.Amount,
|
||||||
|
ConsumerId: v.ConsumerId,
|
||||||
|
ReceiverId: v.ReceiverId,
|
||||||
|
Balance: v.Balance,
|
||||||
|
CreatedTime: v.CreatedTime,
|
||||||
|
ConsumptionType: v.ConsumptionType,
|
||||||
|
}
|
||||||
|
switch v.ConsumptionType {
|
||||||
|
case 2:
|
||||||
|
tempRecord.Title = GetTopicTitle(v.ObjectId)
|
||||||
|
case 4:
|
||||||
|
tempRecord.Title = GetTopicTitle(v.ObjectId)
|
||||||
|
if len(tempRecord.Title) == 0 {
|
||||||
|
tempRecord.ConsumptionType = 10
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 6:
|
||||||
|
fallthrough
|
||||||
|
case 3:
|
||||||
|
fallthrough
|
||||||
|
case 5:
|
||||||
|
fallthrough
|
||||||
|
case 7:
|
||||||
|
replyInfo := GetReply(v.ObjectId)
|
||||||
|
if replyInfo == nil || replyInfo.Deleted {
|
||||||
|
tempRecord.ConsumptionType = 10
|
||||||
|
break
|
||||||
|
}
|
||||||
|
topicInfo := GetTopic(replyInfo.TopicId)
|
||||||
|
if topicInfo == nil || topicInfo.Deleted {
|
||||||
|
tempRecord.ConsumptionType = 10
|
||||||
|
break
|
||||||
|
}
|
||||||
|
tempRecord.Title = topicInfo.Title
|
||||||
|
tempRecord.ObjectId = topicInfo.Id
|
||||||
|
tempRecord.Length = len(replyInfo.Content)
|
||||||
|
case 8:
|
||||||
|
fallthrough
|
||||||
|
case 9:
|
||||||
|
topicInfo := GetTopic(v.ObjectId)
|
||||||
|
if topicInfo == nil || topicInfo.Deleted {
|
||||||
|
tempRecord.ConsumptionType = 10
|
||||||
|
break
|
||||||
|
}
|
||||||
|
tempRecord.ObjectId = v.ObjectId
|
||||||
|
tempRecord.Title = topicInfo.Title
|
||||||
|
tempRecord.Length = len(topicInfo.Content)
|
||||||
|
}
|
||||||
|
res[k] = &tempRecord
|
||||||
|
}(k, v)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
close(errChan)
|
||||||
|
if len(errChan) != 0 {
|
||||||
|
for v := range errChan {
|
||||||
|
panic(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetThanksStatus(memberId string, id, recordType int) bool {
|
||||||
|
record := new(ConsumptionRecord)
|
||||||
|
total, err := adapter.Engine.Where("consumption_type = ?", recordType).And("object_id = ?", id).And("receiver_id = ?", memberId).Count(record)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return total != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTopicConsumption(user *casdoorsdk.User, id int) bool {
|
||||||
|
record := ConsumptionRecord{
|
||||||
|
// Id: util.IntToString(GetConsumptionRecordId()),
|
||||||
|
ReceiverId: GetUserName(user),
|
||||||
|
ObjectId: id,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
ConsumptionType: 8,
|
||||||
|
}
|
||||||
|
record.Amount = CreateTopicCost
|
||||||
|
record.Amount = -record.Amount
|
||||||
|
balance := GetMemberBalance(user)
|
||||||
|
if balance+record.Amount < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
record.Balance = balance + record.Amount
|
||||||
|
AddBalance(&record)
|
||||||
|
UpdateMemberBalance(user, record.Amount)
|
||||||
|
UpdateMemberConsumptionSum(user, -record.Amount)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateReplyConsumption(user *casdoorsdk.User, id int) bool {
|
||||||
|
record := ConsumptionRecord{
|
||||||
|
// Id: util.IntToString(GetConsumptionRecordId()),
|
||||||
|
ReceiverId: GetUserName(user),
|
||||||
|
ObjectId: id,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
ConsumptionType: 6,
|
||||||
|
}
|
||||||
|
record.Amount = CreateReplyCost
|
||||||
|
record.Amount = -record.Amount
|
||||||
|
balance := GetMemberBalance(user)
|
||||||
|
if balance+record.Amount < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
record.Balance = balance + record.Amount
|
||||||
|
AddBalance(&record)
|
||||||
|
UpdateMemberBalance(user, record.Amount)
|
||||||
|
UpdateMemberConsumptionSum(user, -record.Amount)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetReplyBonus(author *casdoorsdk.User, consumer *casdoorsdk.User, id int) {
|
||||||
|
if author.Name == consumer.Name {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
record := ConsumptionRecord{
|
||||||
|
// Id: util.IntToString(GetConsumptionRecordId()),
|
||||||
|
ConsumerId: consumer.Name,
|
||||||
|
ReceiverId: author.Name,
|
||||||
|
ObjectId: id,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
ConsumptionType: 7,
|
||||||
|
}
|
||||||
|
record.Amount = ReceiveReplyBonus
|
||||||
|
balance := GetMemberBalance(consumer)
|
||||||
|
record.Balance = balance + record.Amount
|
||||||
|
AddBalance(&record)
|
||||||
|
UpdateMemberBalance(author, record.Amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TopTopicConsumption(user *casdoorsdk.User, id int) bool {
|
||||||
|
record := ConsumptionRecord{
|
||||||
|
ReceiverId: GetUserName(user),
|
||||||
|
ObjectId: id,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
ConsumptionType: 9,
|
||||||
|
}
|
||||||
|
record.Amount = TopTopicCost
|
||||||
|
record.Amount = -record.Amount
|
||||||
|
balance := GetMemberBalance(user)
|
||||||
|
if balance+record.Amount < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
record.Balance = balance + record.Amount
|
||||||
|
AddBalance(&record)
|
||||||
|
UpdateMemberBalance(user, record.Amount)
|
||||||
|
UpdateMemberConsumptionSum(user, -record.Amount)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,249 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BasicInfo struct {
|
||||||
|
Id string `xorm:"varchar(100) notnull pk"`
|
||||||
|
Value string `xorm:"mediumtext"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
fileDate, version string
|
||||||
|
onlineMemberNum, highestOnlineNum int
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitForumBasicInfo() {
|
||||||
|
GetForumVersion()
|
||||||
|
GetHighestOnlineNum()
|
||||||
|
UpdateOnlineMemberNum()
|
||||||
|
if AutoSyncPeriodSecond >= 30 {
|
||||||
|
fmt.Println("Auto sync from google group enabled.")
|
||||||
|
go AutoSyncGoogleGroup()
|
||||||
|
fmt.Println("Auto sync from gitter room enabled.")
|
||||||
|
go AutoSyncGitter()
|
||||||
|
} else {
|
||||||
|
fmt.Println("Auto sync from google group disabled.")
|
||||||
|
fmt.Println("Auto sync from gitter room disabled.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetForumVersion() string {
|
||||||
|
pwd, _ := os.Getwd()
|
||||||
|
|
||||||
|
fileInfos, err := ioutil.ReadDir(pwd + "/.git/refs/heads")
|
||||||
|
for _, v := range fileInfos {
|
||||||
|
if v.Name() == "master" {
|
||||||
|
if v.ModTime().String() == fileDate {
|
||||||
|
return version
|
||||||
|
} else {
|
||||||
|
fileDate = v.ModTime().String()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := ioutil.ReadFile(pwd + "/.git/refs/heads/master")
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to full length
|
||||||
|
temp := string(content)
|
||||||
|
version = strings.ReplaceAll(temp, "\n", "")
|
||||||
|
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHighestOnlineNum() int {
|
||||||
|
if highestOnlineNum != 0 {
|
||||||
|
return highestOnlineNum
|
||||||
|
}
|
||||||
|
|
||||||
|
info := BasicInfo{Id: "HighestOnlineNum"}
|
||||||
|
existed, err := adapter.Engine.Get(&info)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
highestOnlineNum = util.ParseInt(info.Value)
|
||||||
|
return highestOnlineNum
|
||||||
|
} else {
|
||||||
|
info := BasicInfo{
|
||||||
|
Id: "HighestOnlineNum",
|
||||||
|
Value: "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := adapter.Engine.Insert(&info)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateHighestOnlineNum(num int) bool {
|
||||||
|
highestOnlineNum = num
|
||||||
|
info := new(BasicInfo)
|
||||||
|
info.Value = util.IntToString(num)
|
||||||
|
affected, err := adapter.Engine.Where("id = ?", "HighestOnlineNum").Cols("value").Update(info)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCronJobs() []*CronJob {
|
||||||
|
info := BasicInfo{Id: "CronJobs"}
|
||||||
|
existed, err := adapter.Engine.Get(&info)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
var jobs []*CronJob
|
||||||
|
err := json.Unmarshal([]byte(info.Value), &jobs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return jobs
|
||||||
|
} else {
|
||||||
|
jobs, err := json.Marshal(DefaultCronJobs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
info := BasicInfo{
|
||||||
|
Id: "CronJobs",
|
||||||
|
Value: string(jobs),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = adapter.Engine.Insert(&info)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultCronJobs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCronUpdateJobs() []*UpdateJob {
|
||||||
|
info := BasicInfo{Id: "CronUpdateJobs"}
|
||||||
|
existed, err := adapter.Engine.Get(&info)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
var posts []*UpdateJob
|
||||||
|
err := json.Unmarshal([]byte(info.Value), &posts)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return posts
|
||||||
|
} else {
|
||||||
|
posts, err := json.Marshal(DefaultCronUpdates)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
info := BasicInfo{
|
||||||
|
Id: "CronUpdateJobs",
|
||||||
|
Value: string(posts),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = adapter.Engine.Insert(&info)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultCronUpdates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLatestSyncedRecordId() int {
|
||||||
|
info := BasicInfo{Id: "LatestSyncedRecordId"}
|
||||||
|
existed, err := adapter.Engine.Get(&info)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return util.ParseInt(info.Value)
|
||||||
|
} else {
|
||||||
|
info := BasicInfo{
|
||||||
|
Id: "LatestSyncedRecordId",
|
||||||
|
Value: "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := adapter.Engine.Insert(&info)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateLatestSyncedRecordId(id int) bool {
|
||||||
|
info := new(BasicInfo)
|
||||||
|
info.Value = util.IntToString(id)
|
||||||
|
affected, err := adapter.Engine.Where("id = ?", "LatestSyncedRecordId").Cols("value").Update(info)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOnlineMemberNum returns online member num.
|
||||||
|
func GetOnlineMemberNum() int {
|
||||||
|
if onlineMemberNum == 0 {
|
||||||
|
UpdateOnlineMemberNum()
|
||||||
|
return onlineMemberNum
|
||||||
|
}
|
||||||
|
if onlineMemberNum > highestOnlineNum {
|
||||||
|
UpdateHighestOnlineNum(onlineMemberNum)
|
||||||
|
}
|
||||||
|
return onlineMemberNum
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOnlineMemberNum updates online member num and updates highest online member num at the same time.
|
||||||
|
func UpdateOnlineMemberNum() {
|
||||||
|
onlineMemberNum = GetOnlineUserCount()
|
||||||
|
if onlineMemberNum > highestOnlineNum {
|
||||||
|
UpdateHighestOnlineNum(onlineMemberNum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var recognizeHtmlTags *regexp.Regexp
|
||||||
|
|
||||||
|
func RemoveHtmlTags(text string) string {
|
||||||
|
if recognizeHtmlTags == nil {
|
||||||
|
recognizeHtmlTags = regexp.MustCompile("<[^>]+>")
|
||||||
|
}
|
||||||
|
return recognizeHtmlTags.ReplaceAllString(text, "")
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
"github.com/microcosm-cc/bluemonday"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HasNode(id string) bool {
|
||||||
|
node := GetNode(id)
|
||||||
|
|
||||||
|
return node != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HasTab(id string) bool {
|
||||||
|
tab := GetTab(id)
|
||||||
|
|
||||||
|
return tab != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HasPlane(id string) bool {
|
||||||
|
plane := GetPlane(id)
|
||||||
|
|
||||||
|
return plane != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsForbidden check member whether is forbidden.
|
||||||
|
func IsForbidden(user *casdoorsdk.User) bool {
|
||||||
|
return user.IsForbidden
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterUnsafeHTML(content string) string {
|
||||||
|
if content == "" {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
p := bluemonday.UGCPolicy()
|
||||||
|
p.AllowAttrs("style").OnElements("span")
|
||||||
|
p.AllowElements("video")
|
||||||
|
p.AllowAttrs("width").OnElements("video")
|
||||||
|
p.AllowAttrs("controls").OnElements("video")
|
||||||
|
p.AllowAttrs("src").OnElements("source")
|
||||||
|
p.AllowAttrs("type").OnElements("source")
|
||||||
|
p.AllowAttrs("style").OnElements("video")
|
||||||
|
res := p.Sanitize(content)
|
||||||
|
return res
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import "github.com/astaxie/beego"
|
||||||
|
|
||||||
|
var (
|
||||||
|
DefaultPageNum = 20
|
||||||
|
DefaultHomePageNum = 50
|
||||||
|
DefaultNotificationPageNum = 10
|
||||||
|
DefaultBalancePageNum = 25
|
||||||
|
DefaultFilePageNum = 25
|
||||||
|
DefaultMemberAdminPageNum = 100
|
||||||
|
DefaultRenameQuota = 3
|
||||||
|
UserNamingRestrictions = false
|
||||||
|
HomePageNodeNum = 8
|
||||||
|
TopicThanksCost = 15
|
||||||
|
ReplyThanksCost = 10
|
||||||
|
CreateTopicCost = 20
|
||||||
|
CreateReplyCost = 5
|
||||||
|
TopTopicCost = 200
|
||||||
|
ReceiveReplyBonus = 5
|
||||||
|
MaxDailyCheckinBonus = 20
|
||||||
|
LatestNodeNum = 20
|
||||||
|
HotNodeNum = 15
|
||||||
|
HotTopicNum = 10
|
||||||
|
TopicEditableTime = 10.0 // minutes
|
||||||
|
ReplyEditableTime = 10.0 // minutes
|
||||||
|
ReplyDeletableTime = 5.0 // minutes
|
||||||
|
NodeHitRecordExpiredTime = 1 // month
|
||||||
|
TopicHitRecordExpiredTime = 1 // day
|
||||||
|
ValidateCodeExpiredTime = 20 // minutes
|
||||||
|
DefaultTopTopicTime = 10 // minutes
|
||||||
|
OnlineMemberExpiedTime = 10 // minutes
|
||||||
|
DefaultUploadFileQuota = 50
|
||||||
|
Domain = beego.AppConfig.String("domain") // domain
|
||||||
|
AutoSyncPeriodSecond = -1 // auto sync is disabled if < 30
|
||||||
|
|
||||||
|
DefaultCronJobs = []*CronJob{
|
||||||
|
{
|
||||||
|
Id: "updateExpiredData",
|
||||||
|
BumpTime: "0:0",
|
||||||
|
State: "active",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "updateHotInfo",
|
||||||
|
BumpTime: "*/1:*",
|
||||||
|
State: "active",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "expireData",
|
||||||
|
BumpTime: "*/1:*",
|
||||||
|
State: "active",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
DefaultCronUpdates = []*UpdateJob{
|
||||||
|
{
|
||||||
|
Id: "expireData",
|
||||||
|
JobId: "updateExpiredData",
|
||||||
|
State: "active",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "hotInfo",
|
||||||
|
JobId: "updateHotInfo",
|
||||||
|
State: "active",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "expireTopTopic",
|
||||||
|
JobId: "expireData",
|
||||||
|
State: "active",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "expireOnlineMember",
|
||||||
|
JobId: "expireData",
|
||||||
|
State: "active",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
|
@ -0,0 +1,142 @@
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mileusna/crontab"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CronJob struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
BumpTime string `json:"bumpTime"`
|
||||||
|
State string `json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateJob struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
JobId string `json:"jobId"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctab *crontab.Crontab
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ctab = crontab.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
func schedulePost(postId string) {
|
||||||
|
post := GetUpdateJob(postId)
|
||||||
|
isUpdated, num := post.updateInfo()
|
||||||
|
if isUpdated && num != 0 {
|
||||||
|
fmt.Printf("Update forum info: %s, update num: %d\n", post.Id, num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (job *UpdateJob) updateInfo() (bool, int) {
|
||||||
|
var num int
|
||||||
|
switch job.Id {
|
||||||
|
case "expireData":
|
||||||
|
expiredNodeDate := util.GetTimeMonth(-NodeHitRecordExpiredTime)
|
||||||
|
expiredTopicDate := util.GetTimeDay(-TopicHitRecordExpiredTime)
|
||||||
|
|
||||||
|
updateNodeNum := ChangeExpiredDataStatus(1, expiredNodeDate)
|
||||||
|
updateTopicNum := ChangeExpiredDataStatus(2, expiredTopicDate)
|
||||||
|
|
||||||
|
num = updateNodeNum + updateTopicNum
|
||||||
|
case "hotInfo":
|
||||||
|
last := GetLastRecordId()
|
||||||
|
latest := GetLatestSyncedRecordId()
|
||||||
|
if last == latest {
|
||||||
|
num = 0
|
||||||
|
} else {
|
||||||
|
UpdateLatestSyncedRecordId(last)
|
||||||
|
updateNodeNum := UpdateHotNode(latest)
|
||||||
|
updateTopicNum := UpdateHotTopic(latest)
|
||||||
|
|
||||||
|
num = updateTopicNum + updateNodeNum
|
||||||
|
}
|
||||||
|
case "expireTopTopic":
|
||||||
|
num = ExpireTopTopic()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, num
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUpdateJob(id string) *UpdateJob {
|
||||||
|
posts := GetCronUpdateJobs()
|
||||||
|
for _, v := range posts {
|
||||||
|
if v.Id == id {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &UpdateJob{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetJobs() []*CronJob {
|
||||||
|
return GetCronJobs()
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUpdateJobs(jobId string) []*UpdateJob {
|
||||||
|
posts := GetCronUpdateJobs()
|
||||||
|
var jobs []*UpdateJob
|
||||||
|
for _, v := range posts {
|
||||||
|
if v.JobId == jobId {
|
||||||
|
jobs = append(jobs, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return jobs
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDumpTime(bumpTime string) (string, string) {
|
||||||
|
tokens := strings.Split(bumpTime, ":")
|
||||||
|
return tokens[0], tokens[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func refreshCronTasks() bool {
|
||||||
|
ctab.Clear()
|
||||||
|
|
||||||
|
jobs := GetJobs()
|
||||||
|
for _, job := range jobs {
|
||||||
|
if job.State != "active" || job.BumpTime == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
hours, minutes := parseDumpTime(job.BumpTime)
|
||||||
|
|
||||||
|
posts := GetUpdateJobs(job.Id)
|
||||||
|
for _, post := range posts {
|
||||||
|
if post.State != "active" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
schedule := fmt.Sprintf("%s %s * * *", minutes, hours)
|
||||||
|
// schedule := "* * * * *"
|
||||||
|
err := ctab.AddJob(schedule, schedulePost, post.Id)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func timerRoutine() {
|
||||||
|
for range time.Tick(time.Second * 3600) {
|
||||||
|
refreshCronTasks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitTimer initializes scheduled tasks.
|
||||||
|
func InitTimer() {
|
||||||
|
refreshCronTasks()
|
||||||
|
|
||||||
|
go timerRoutine()
|
||||||
|
}
|
|
@ -0,0 +1,267 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Favorites struct {
|
||||||
|
Id int `xorm:"int notnull pk autoincr" json:"id"`
|
||||||
|
FavoritesType string `xorm:"varchar(100) index" json:"favoritesType"`
|
||||||
|
ObjectId string `xorm:"varchar(100) index" json:"objectId"`
|
||||||
|
CreatedTime string `xorm:"varchar(40)" json:"createdTime"`
|
||||||
|
MemberId string `xorm:"varchar(100) index" json:"memberId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
FavorTopic = "favor_topic"
|
||||||
|
FollowUser = "follow_user"
|
||||||
|
FavorNode = "favor_node"
|
||||||
|
SubscribeTopic = "subscribe_topic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsFavoritesExist(Type string) bool {
|
||||||
|
// check the if the string is in the enum
|
||||||
|
if Type == FavorTopic || Type == FollowUser || Type == FavorNode || Type == SubscribeTopic {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddFavorites(favorite *Favorites) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(favorite)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddMemberFavorites(memberId string, favoritesType string, objectId string) bool {
|
||||||
|
status := GetFavoritesStatus(memberId, favoritesType, objectId)
|
||||||
|
if status == true {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
favorite := Favorites{
|
||||||
|
FavoritesType: favoritesType,
|
||||||
|
ObjectId: objectId,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
MemberId: memberId,
|
||||||
|
}
|
||||||
|
return AddFavorites(&favorite)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteFavorites(memberId string, objectId string, favoritesType string) bool {
|
||||||
|
affected, err := adapter.Engine.Where("favorites_type = ?", favoritesType).And("object_id = ?", objectId).And("member_id = ?", memberId).Delete(&Favorites{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFavoritesCount() int {
|
||||||
|
count, err := adapter.Engine.Count(&Favorites{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFavoritesStatus(memberId string, objectId string, favoritesType string) bool {
|
||||||
|
node := new(Favorites)
|
||||||
|
total, err := adapter.Engine.Where("favorites_type = ?", favoritesType).And("object_id = ?", objectId).And("member_id = ?", memberId).Count(node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return total != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicsFromFavorites(memberId string, limit int, offset int, favoritesType string) []*TopicWithAvatar {
|
||||||
|
favorites := []*Favorites{}
|
||||||
|
err := adapter.Engine.Where("member_id = ?", memberId).And("favorites_type = ?", favoritesType).Limit(limit, offset).Find(&favorites)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
topics := []*TopicWithAvatar{}
|
||||||
|
for _, v := range favorites {
|
||||||
|
topicId := util.ParseInt(v.ObjectId)
|
||||||
|
temp := GetTopicWithAvatar(topicId, nil)
|
||||||
|
topics = append(topics, temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return topics
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMembersFromFavorites(objectId string, favoritesType string) []*casdoorsdk.User {
|
||||||
|
favorites := []*Favorites{}
|
||||||
|
err := adapter.Engine.Where("object_id = ?", objectId).And("favorites_type = ?", favoritesType).Find(&favorites)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
members := []*casdoorsdk.User{}
|
||||||
|
for _, v := range favorites {
|
||||||
|
user := GetUser(v.MemberId)
|
||||||
|
if user != nil {
|
||||||
|
members = append(members, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return members
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRepliesFromFavorites(memberId string, limit int, offset int, favoritesType string) []*ReplyWithAvatar {
|
||||||
|
favorites := []*Favorites{}
|
||||||
|
err := adapter.Engine.Where("member_id = ?", memberId).And("favorites_type = ?", favoritesType).Limit(limit, offset).Find(&favorites)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
replies := []*ReplyWithAvatar{}
|
||||||
|
for _, v := range favorites {
|
||||||
|
topicId := util.ParseInt(v.ObjectId)
|
||||||
|
temp, _ := GetReplies(topicId, nil, limit, offset)
|
||||||
|
for _, v := range temp {
|
||||||
|
replies = append(replies, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return replies
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFollowingNewAction(memberId string, limit int, offset int) []*TopicWithAvatar {
|
||||||
|
var topics []*TopicWithAvatar
|
||||||
|
|
||||||
|
err := adapter.Engine.Table("topic").
|
||||||
|
Join("INNER", "favorites", "favorites.object_id = topic.author").
|
||||||
|
Where("favorites.member_id = ?", memberId).And("favorites.favorites_type = ?", FollowUser).
|
||||||
|
Desc("topic.id").
|
||||||
|
Cols("topic.id, topic.author, topic.node_id, topic.node_name, topic.title, topic.created_time, topic.last_reply_user, topic.last_Reply_time, topic.reply_count, topic.favorite_count, topic.deleted, topic.home_page_top_time, topic.tab_top_time, topic.node_top_time").
|
||||||
|
Omit("topic.content").
|
||||||
|
Limit(limit, offset).Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, topic := range topics {
|
||||||
|
topic.Avatar = getUserAvatar(topic.Author)
|
||||||
|
}
|
||||||
|
|
||||||
|
return topics
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNodesFromFavorites(memberId string, limit int, offset int) []*NodeFavoritesRes {
|
||||||
|
favorites := []*Favorites{}
|
||||||
|
err := adapter.Engine.Where("member_id = ?", memberId).And("favorites_type = ?", FavorNode).Limit(limit, offset).Find(&favorites)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes := []*NodeFavoritesRes{}
|
||||||
|
for _, v := range favorites {
|
||||||
|
var temp NodeFavoritesRes
|
||||||
|
temp.NodeInfo = GetNode(v.ObjectId)
|
||||||
|
temp.TopicNum = GetNodeTopicNum(v.ObjectId)
|
||||||
|
nodes = append(nodes, &temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNodeFavoritesNum(id string) int {
|
||||||
|
node := new(Favorites)
|
||||||
|
total, err := adapter.Engine.Where("favorites_type = ?", FavorNode).And("object_id = ?", id).Count(node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicFavoritesNum(id string) int {
|
||||||
|
topic := new(Favorites)
|
||||||
|
total, err := adapter.Engine.Where("favorites_type = ?", FavorTopic).And("object_id = ?", id).Count(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicSubscribeNum(id string) int {
|
||||||
|
topic := new(Favorites)
|
||||||
|
total, err := adapter.Engine.Where("favorites_type = ?", SubscribeTopic).And("object_id = ?", id).Count(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFollowingNum(id string) int {
|
||||||
|
member := new(Favorites)
|
||||||
|
total, err := adapter.Engine.Where("favorites_type = ?", FollowUser).And("member_id = ?", id).Count(member)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFavoritesNum(favoritesType string, memberId string) int {
|
||||||
|
var total int64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch favoritesType {
|
||||||
|
case FavorTopic:
|
||||||
|
topic := new(Favorites)
|
||||||
|
total, err = adapter.Engine.Where("favorites_type = ?", FavorTopic).And("member_id = ?", memberId).Count(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case FollowUser:
|
||||||
|
topic := new(Favorites)
|
||||||
|
total, err = adapter.Engine.Table("topic").Join("INNER", "favorites", "topic.author = favorites.object_id").Where("favorites.member_id = ?", memberId).And("favorites.favorites_type = ?", FollowUser).Count(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case FavorNode:
|
||||||
|
node := new(Favorites)
|
||||||
|
total, err = adapter.Engine.Where("favorites_type = ?", FavorNode).And("member_id = ?", memberId).Count(node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case SubscribeTopic:
|
||||||
|
topic := new(Favorites)
|
||||||
|
total, err = adapter.Engine.Where("favorites_type = ?", SubscribeTopic).And("member_id = ?", memberId).Count(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import "github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
|
||||||
|
type UploadFileRecord struct {
|
||||||
|
Id int `xorm:"int notnull pk autoincr" json:"id"`
|
||||||
|
FileName string `xorm:"varchar(100)" json:"fileName"`
|
||||||
|
FilePath string `xorm:"varchar(100)" json:"filePath"`
|
||||||
|
FileUrl string `xorm:"varchar(100)" json:"fileUrl"`
|
||||||
|
FileType string `xorm:"varchar(10)" json:"fileType"`
|
||||||
|
FileExt string `xorm:"varchar(20)" json:"fileExt"`
|
||||||
|
MemberId string `xorm:"varchar(100) index" json:"memberId"`
|
||||||
|
CreatedTime string `xorm:"varchar(40)" json:"createdTime"`
|
||||||
|
Size int `xorm:"int" json:"size"`
|
||||||
|
Views int `xorm:"int" json:"views"`
|
||||||
|
Desc string `xorm:"varchar(500)" json:"desc"`
|
||||||
|
Deleted bool `xorm:"bool" json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddFileRecord(record *UploadFileRecord) (bool, int) {
|
||||||
|
affected, err := adapter.Engine.Insert(record)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0, record.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFile(id int) *UploadFileRecord {
|
||||||
|
file := UploadFileRecord{Id: id}
|
||||||
|
existed, err := adapter.Engine.Get(&file)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &file
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFiles(memberId string, limit, offset int) []*UploadFileRecord {
|
||||||
|
records := []*UploadFileRecord{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Where("member_id = ?", memberId).And("deleted = ?", 0).Limit(limit, offset).Find(&records)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
//func GetFilesByMember(memberId string) []*UploadFileRecord {
|
||||||
|
// records := []*UploadFileRecord{}
|
||||||
|
// err := adapter.Engine.Where("member_id = ?", memberId).Find(&records)
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return records
|
||||||
|
//}
|
||||||
|
|
||||||
|
func DeleteFilesByMember(memberId string) bool {
|
||||||
|
affected, err := adapter.Engine.Where("member_id = ?", memberId).Delete(&UploadFileRecord{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFilesNum(memberId string) int {
|
||||||
|
var total int64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
record := new(UploadFileRecord)
|
||||||
|
total, err = adapter.Engine.Where("member_id = ?", memberId).And("deleted = ?", 0).Count(record)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteFileRecord(id int) bool {
|
||||||
|
record := new(UploadFileRecord)
|
||||||
|
record.Deleted = true
|
||||||
|
affected, err := adapter.Engine.Id(id).Cols("deleted").Update(record)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func FileEditable(user *casdoorsdk.User, author string) bool {
|
||||||
|
if CheckIsAdmin(user) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if GetUserName(user) != author {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddFileViewsNum(id int) bool {
|
||||||
|
file := GetFile(id)
|
||||||
|
if file == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Views++
|
||||||
|
affected, err := adapter.Engine.Id(id).Cols("views").Update(file)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateFileDescribe(id int, fileName, desc string) bool {
|
||||||
|
file := GetFile(id)
|
||||||
|
if file == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Desc = desc
|
||||||
|
file.FileName = fileName
|
||||||
|
affected, err := adapter.Engine.Id(id).Cols("desc, file_name").Update(file)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
type FrontConf struct {
|
||||||
|
Id string `xorm:"varchar(100) notnull pk" json:"id"`
|
||||||
|
Value string `xorm:"mediumtext" json:"value"`
|
||||||
|
Field string `xorm:"varchar(100)" json:"field"`
|
||||||
|
Tags []string `xorm:"varchar(200)" json:"tags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var Confs = []*FrontConf{
|
||||||
|
{Id: "forumName", Value: "Casnode", Field: "visualConf", Tags: nil},
|
||||||
|
{Id: "logoImage", Value: "https://cdn.casbin.com/forum/static/img/logo.png", Field: "visualConf", Tags: nil},
|
||||||
|
{Id: "footerLogoImage", Value: "https://cdn.casbin.com/forum/static/img/logo-footer.png", Field: "visualConf", Tags: nil},
|
||||||
|
{Id: "footerLogoUrl", Value: "https://www.digitalocean.com/", Field: "visualConf", Tags: nil},
|
||||||
|
{Id: "signinBoxStrong", Value: "Casbin = way to authorization", Field: "visualConf", Tags: nil},
|
||||||
|
{Id: "signinBoxSpan", Value: "A place for Casbin developers and users", Field: "visualConf", Tags: nil},
|
||||||
|
{Id: "footerDeclaration", Value: "World is powered by code", Field: "visualConf", Tags: nil},
|
||||||
|
{Id: "footerAdvise", Value: "♥ Do have faith in what you're doing.", Field: "visualConf", Tags: nil},
|
||||||
|
{Id: "faq", Value: "Not yet", Field: "", Tags: nil},
|
||||||
|
{Id: "mission", Value: "Not yet", Field: "", Tags: nil},
|
||||||
|
{Id: "advertise", Value: "Not yet", Field: "", Tags: nil},
|
||||||
|
{Id: "thanks", Value: "Not yet", Field: "", Tags: nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitFrontConf() {
|
||||||
|
var confs []*FrontConf
|
||||||
|
err := adapter.Engine.Find(&confs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if len(confs) > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
confs = Confs
|
||||||
|
_, err = adapter.Engine.Insert(&confs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFrontConfById(id string) *FrontConf {
|
||||||
|
var confs []*FrontConf
|
||||||
|
err := adapter.Engine.Where("id = ?", id).Find(&confs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(confs) == 0 {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return confs[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFrontConfsByField(field string) []*FrontConf {
|
||||||
|
var confs []*FrontConf
|
||||||
|
err := adapter.Engine.Where("field = ?", field).Find(&confs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return confs
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateFrontConfs(confs []*FrontConf) bool {
|
||||||
|
var err error
|
||||||
|
for _, conf := range confs {
|
||||||
|
_, err = adapter.Engine.Where("id = ?", conf.Id).Update(conf)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateFrontConfById(id string, value string, tags []string) (int64, error) {
|
||||||
|
return adapter.Engine.Id(id).Cols("value", "tags").Update(&FrontConf{Value: value, Tags: tags})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateFrontConfsByField(confs []*FrontConf, field string) error {
|
||||||
|
for _, conf := range confs {
|
||||||
|
if conf.Field == field {
|
||||||
|
_, err := adapter.Engine.Id(conf.Id).Cols("value").Update(conf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,494 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
"github.com/casbin/casnode/service"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
"github.com/sromku/go-gitter"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
topicDuration = "4" // Hours
|
||||||
|
apiLIMIT = 10 // request frequency
|
||||||
|
)
|
||||||
|
|
||||||
|
type topicGitter struct {
|
||||||
|
Topic Topic
|
||||||
|
Massages []gitter.Message
|
||||||
|
MemberMsgMap map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
roomSyncMsgHeadMap = map[string]string{}
|
||||||
|
roomSyncMsgTailMap = map[string]string{}
|
||||||
|
lastMsgMap = map[string]gitter.Message{}
|
||||||
|
lastTopicMap = map[string]topicGitter{}
|
||||||
|
currentTopicMap = map[string]topicGitter{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func AutoSyncGitter() {
|
||||||
|
if AutoSyncPeriodSecond < 30 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncAllGitterRooms()
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Duration(AutoSyncPeriodSecond) * time.Second)
|
||||||
|
SyncAllGitterRooms()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SyncAllGitterRooms() {
|
||||||
|
fmt.Println("Sync from gitter room started...")
|
||||||
|
var nodes []Node
|
||||||
|
err := adapter.Engine.Find(&nodes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
node.SyncGitter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n Node) SyncGitter() {
|
||||||
|
if n.GitterRoomURL == "" || n.GitterApiToken == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
handleErr(err.(error))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Get your own token At https://developer.gitter.im/
|
||||||
|
api := gitter.New(n.GitterApiToken)
|
||||||
|
|
||||||
|
// get room id by room url
|
||||||
|
rooms, err := api.GetRooms()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println("gitter room urls:", n.GitterRoomURL)
|
||||||
|
|
||||||
|
room := gitter.Room{}
|
||||||
|
for _, v := range rooms { // find RoomId by url
|
||||||
|
if "https://gitter.im/"+v.URI == n.GitterRoomURL {
|
||||||
|
room = v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if room.Name == "" {
|
||||||
|
panic(errors.New("room is not exist"))
|
||||||
|
}
|
||||||
|
|
||||||
|
topics := n.GetAllTopicsByNode()
|
||||||
|
topicNum := len(topics)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
messages := []gitter.Message{}
|
||||||
|
|
||||||
|
// get sync index, it is the last sync message id
|
||||||
|
headIdx, ok := roomSyncMsgHeadMap[room.ID]
|
||||||
|
if !ok { // get all msg if idx is not exist
|
||||||
|
for _, topic := range topics {
|
||||||
|
if topic.GitterMessageId != "" {
|
||||||
|
// get reply
|
||||||
|
replies := GetRepliesOfTopic(topic.Id)
|
||||||
|
// get sync msg idx
|
||||||
|
num := len(replies)
|
||||||
|
if num == 0 {
|
||||||
|
headIdx = topic.GitterMessageId
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
flag := false
|
||||||
|
for i := num - 1; i >= 0; i-- {
|
||||||
|
if replies[i].GitterMessageId != "" {
|
||||||
|
headIdx = replies[i].GitterMessageId
|
||||||
|
flag = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if flag {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the api limits the number of messages to 50
|
||||||
|
messages, err = api.GetMessages(room.ID, &gitter.Pagination{
|
||||||
|
AfterID: headIdx,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if len(messages) == 0 {
|
||||||
|
roomSyncMsgHeadMap[room.ID] = headIdx
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < apiLIMIT; i++ { // restrict request frequency
|
||||||
|
msgs, err := api.GetMessages(room.ID, &gitter.Pagination{
|
||||||
|
AfterID: messages[len(messages)-1].ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if len(msgs) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
messages = append(messages, msgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("sync msg for room(msgNum:%d): %s\n", len(messages), room.Name)
|
||||||
|
createTopicWithMessages(messages, room, n, topics, true)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// tail
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
messages := []gitter.Message{}
|
||||||
|
|
||||||
|
tailIdx, ok := roomSyncMsgTailMap[room.ID]
|
||||||
|
if !ok {
|
||||||
|
for i := topicNum - 1; i >= 0; i-- {
|
||||||
|
topic := topics[i]
|
||||||
|
if topic.GitterMessageId != "" {
|
||||||
|
tailIdx = topic.GitterMessageId
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t := time.Time{}
|
||||||
|
tExist := true // if t is not exist, sync all msg
|
||||||
|
if n.GitterSyncFromTime == "" {
|
||||||
|
tExist = false
|
||||||
|
} else {
|
||||||
|
t, err = time.Parse(time.RFC3339, n.GitterSyncFromTime)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tailIdx != "" {
|
||||||
|
tailMsg, err := api.GetMessage(room.ID, tailIdx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tExist {
|
||||||
|
if tailMsg.Sent.Before(t) { // if msg is before the start time, end sync tail
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
messages, err = api.GetMessages(room.ID, nil)
|
||||||
|
if len(messages) != 0 {
|
||||||
|
tailIdx = messages[0].ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
messages, err = api.GetMessages(room.ID, &gitter.Pagination{
|
||||||
|
BeforeID: tailIdx,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if len(messages) == 0 {
|
||||||
|
roomSyncMsgTailMap[room.ID] = tailIdx
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < apiLIMIT; i++ { // restrict request frequency
|
||||||
|
msgs, err := api.GetMessages(room.ID, &gitter.Pagination{
|
||||||
|
BeforeID: messages[0].ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
num := len(msgs)
|
||||||
|
if num == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// if msg is before the start time, end sync tail
|
||||||
|
if tExist {
|
||||||
|
if msgs[0].Sent.Before(t) {
|
||||||
|
for i := num - 1; i > 0; i-- {
|
||||||
|
if msgs[i].Sent.Before(t) {
|
||||||
|
if i == num-1 {
|
||||||
|
msgs = []gitter.Message{}
|
||||||
|
} else {
|
||||||
|
msgs = msgs[i+1:]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
messages = append(msgs, messages...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
messages = append(msgs, messages...)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("sync msg for room(msgNum:%d): %s\n", len(messages), room.Name)
|
||||||
|
createTopicWithMessages(messages, room, n, topics, false)
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// main create topic func
|
||||||
|
func createTopicWithMessages(messages []gitter.Message, room gitter.Room, node Node, topics []Topic, asc bool) {
|
||||||
|
GetTopicExist := func(topicTitle string) Topic {
|
||||||
|
for _, topic := range topics {
|
||||||
|
if topic.Title == topicTitle {
|
||||||
|
return topic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Topic{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize value
|
||||||
|
lastMsg, ok := lastMsgMap[room.ID]
|
||||||
|
if !ok {
|
||||||
|
lastMsg = gitter.Message{}
|
||||||
|
}
|
||||||
|
lastTopic := lastTopicMap[room.ID]
|
||||||
|
if !ok {
|
||||||
|
lastTopic = topicGitter{MemberMsgMap: map[string]int{}}
|
||||||
|
}
|
||||||
|
currentTopic, ok := currentTopicMap[room.ID]
|
||||||
|
if !ok {
|
||||||
|
currentTopic = topicGitter{MemberMsgMap: map[string]int{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range messages {
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
handleErr(err.(error))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// create if user is not exist
|
||||||
|
user, err := casdoorsdk.GetUser(msg.From.Username)
|
||||||
|
// fmt.Println("user:", user)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if user.Id == "" { // add user
|
||||||
|
avatar := getGitterAvatarUrl(msg.From.Username, msg.From.AvatarURLMedium) // if error, avatar will be ""
|
||||||
|
newUser := casdoorsdk.User{
|
||||||
|
Name: msg.From.Username,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
UpdatedTime: util.GetCurrentTime(),
|
||||||
|
DisplayName: msg.From.DisplayName,
|
||||||
|
Avatar: avatar,
|
||||||
|
SignupApplication: CasdoorApplication,
|
||||||
|
}
|
||||||
|
fmt.Println("add user: ", newUser.Name)
|
||||||
|
_, err := casdoorsdk.AddUser(&newUser)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mentioned := false // if @user
|
||||||
|
for _, mention := range msg.Mentions {
|
||||||
|
if mention.ScreenName == lastMsg.From.Username {
|
||||||
|
mentioned = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if @user and lastMsg is not @user, then create topic
|
||||||
|
// if duration is more than 4 hour, then create topic
|
||||||
|
|
||||||
|
d := msg.Sent.Sub(lastMsg.Sent)
|
||||||
|
dur, err := strconv.Atoi(topicDuration)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if d > time.Hour*time.Duration(dur) && !mentioned { // if dur > `TopicDuration` and not @user last replied
|
||||||
|
tmpStr := []rune(msg.Text)
|
||||||
|
if len(tmpStr) > 200 { // limit length
|
||||||
|
tmpStr = tmpStr[:200]
|
||||||
|
}
|
||||||
|
title := string(tmpStr)
|
||||||
|
|
||||||
|
topic := GetTopicExist(title)
|
||||||
|
if topic.Id == 0 { // not exist
|
||||||
|
// add topic
|
||||||
|
topic = Topic{
|
||||||
|
Author: msg.From.Username,
|
||||||
|
NodeId: node.Id,
|
||||||
|
NodeName: node.Name,
|
||||||
|
TabId: node.TabId,
|
||||||
|
Title: title,
|
||||||
|
CreatedTime: util.Time2String(msg.Sent),
|
||||||
|
LastReplyTime: util.Time2String(msg.Sent),
|
||||||
|
Tags: service.Finalword(msg.Text),
|
||||||
|
EditorType: "markdown",
|
||||||
|
Content: msg.Text,
|
||||||
|
GitterMessageId: msg.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, topicID := AddTopic(&topic)
|
||||||
|
topic.Id = topicID
|
||||||
|
}
|
||||||
|
|
||||||
|
// deep copy
|
||||||
|
data, _ := json.Marshal(currentTopic)
|
||||||
|
_ = json.Unmarshal(data, &lastTopic)
|
||||||
|
lastTopicMap[room.ID] = lastTopic
|
||||||
|
|
||||||
|
// new currentTopic
|
||||||
|
currentTopic = topicGitter{Topic: topic, MemberMsgMap: map[string]int{}}
|
||||||
|
currentTopic.Massages = append(currentTopic.Massages, msg)
|
||||||
|
currentTopic.MemberMsgMap[msg.From.Username]++
|
||||||
|
currentTopicMap[room.ID] = currentTopic
|
||||||
|
} else {
|
||||||
|
// add reply to lastTopic
|
||||||
|
reply := Reply{
|
||||||
|
Author: msg.From.Username,
|
||||||
|
TopicId: currentTopic.Topic.Id,
|
||||||
|
CreatedTime: util.Time2String(msg.Sent),
|
||||||
|
Tags: service.Finalword(msg.Text),
|
||||||
|
EditorType: "markdown",
|
||||||
|
Content: msg.Text,
|
||||||
|
GitterMessageId: msg.ID,
|
||||||
|
}
|
||||||
|
_, _ = AddReply(&reply)
|
||||||
|
|
||||||
|
ChangeTopicReplyCount(reply.TopicId, 1)
|
||||||
|
ChangeTopicLastReplyUser(currentTopic.Topic.Id, msg.From.Username, util.Time2String(msg.Sent))
|
||||||
|
|
||||||
|
currentTopic.Massages = append(currentTopic.Massages, msg)
|
||||||
|
currentTopic.MemberMsgMap[msg.From.Username]++
|
||||||
|
currentTopicMap[room.ID] = currentTopic
|
||||||
|
}
|
||||||
|
|
||||||
|
// deep copy
|
||||||
|
data, _ := json.Marshal(msg)
|
||||||
|
_ = json.Unmarshal(data, &lastMsg)
|
||||||
|
|
||||||
|
// add index to sync message
|
||||||
|
if asc {
|
||||||
|
roomSyncMsgHeadMap[room.ID] = msg.ID
|
||||||
|
} else {
|
||||||
|
roomSyncMsgTailMap[room.ID] = messages[0].ID
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleErr(err error) {
|
||||||
|
var stack string
|
||||||
|
logs.Critical("Handler crashed with error:", err)
|
||||||
|
for i := 1; ; i++ {
|
||||||
|
_, file, line, ok := runtime.Caller(i)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logs.Critical(fmt.Sprintf("%s:%d", file, line))
|
||||||
|
stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGitterAvatarUrl(username string, avatar string) string {
|
||||||
|
var fileBytes []byte
|
||||||
|
var fileExt string
|
||||||
|
var err error
|
||||||
|
times := 0
|
||||||
|
for {
|
||||||
|
fileBytes, _, err = downloadFile(avatar)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
times += 1
|
||||||
|
fmt.Printf("[%d]: downloadFile() error: %s, times = %d\n", username, err.Error(), times)
|
||||||
|
if times >= 10 {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExt = ".png"
|
||||||
|
|
||||||
|
avatarUrl, err := uploadGitterAvatar(username, fileBytes, fileExt)
|
||||||
|
if err != nil {
|
||||||
|
avatarUrl = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return avatarUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
func uploadGitterAvatar(username string, fileBytes []byte, fileExt string) (string, error) {
|
||||||
|
username = url.QueryEscape(username)
|
||||||
|
memberId := fmt.Sprintf("%s/%s", CasdoorOrganization, username)
|
||||||
|
fileUrl, err := service.UploadFileToStorageSafe(memberId, "avatar", "uploadGitterAvatar", fmt.Sprintf("avatar/%s%s", memberId, fileExt), fileBytes, "", "")
|
||||||
|
return fileUrl, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadFile(url string) ([]byte, string, error) {
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
err := Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(resp.Body)
|
||||||
|
|
||||||
|
bs, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
newUrl := resp.Request.URL.String()
|
||||||
|
return bs, newUrl, nil
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright 2022 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/issue9/assert"
|
||||||
|
"github.com/sromku/go-gitter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemoveSyncGitterData(t *testing.T) {
|
||||||
|
InitConfig()
|
||||||
|
InitAdapter()
|
||||||
|
|
||||||
|
// delete all sync gitter data
|
||||||
|
var nodes []Node
|
||||||
|
err := adapter.Engine.Find(&nodes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node.GitterRoomURL == "" || node.GitterApiToken == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
api := gitter.New(node.GitterApiToken)
|
||||||
|
rooms, err := api.GetRooms()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
url := node.GitterRoomURL
|
||||||
|
room := gitter.Room{}
|
||||||
|
for _, v := range rooms { // find RoomId by url
|
||||||
|
if "https://gitter.im/"+v.URI == url {
|
||||||
|
room = v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.NotEqual(t, room.Name, "")
|
||||||
|
adapter.Engine.ShowSQL(true)
|
||||||
|
_, err = adapter.Engine.
|
||||||
|
Query("DELETE t.*,r.* FROM topic as t LEFT JOIN reply as r ON t.id = r.topic_id WHERE t.gitter_message_id is not null AND t.node_id = ?", node.Id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("INFO: delete sync gitter data of room: %s\n", room.Name)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
// RecordType: 1 means node hit record
|
||||||
|
type BrowseRecord struct {
|
||||||
|
Id int `xorm:"int notnull pk autoincr" json:"id"`
|
||||||
|
MemberId string `xorm:"varchar(100)" json:"memberId"`
|
||||||
|
RecordType int `xorm:"int" json:"recordType"`
|
||||||
|
ObjectId string `xorm:"varchar(100) index" json:"objectId"`
|
||||||
|
CreatedTime string `xorm:"varchar(40) index" json:"createdTime"`
|
||||||
|
Expired bool `xorm:"bool" json:"expired"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBrowseRecordNum(recordType int, objectId string) int {
|
||||||
|
record := new(BrowseRecord)
|
||||||
|
total, err := adapter.Engine.Where("object_id = ?", objectId).And("record_type = ?", recordType).And("expired = ?", false).Count(record)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeletedExpiredData(recordType int, date string) bool {
|
||||||
|
affected, err := adapter.Engine.Where("record_type = ?", recordType).And("date < ?", date).Delete(&BrowseRecord{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddBrowseRecordNum(record *BrowseRecord) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(record)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeExpiredDataStatus(recordType int, date string) int {
|
||||||
|
var res int
|
||||||
|
record := new(BrowseRecord)
|
||||||
|
record.Expired = true
|
||||||
|
affected, err := adapter.Engine.Where("record_type = ?", recordType).And("expired = ?", 0).And("created_time < ?", date).Cols("expired").Update(record)
|
||||||
|
res += int(affected)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLastRecordId() int {
|
||||||
|
record := new(BrowseRecord)
|
||||||
|
_, err := adapter.Engine.Desc("id").Cols("id").Limit(1).Get(record)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := record.Id
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateHotNode(last int) int {
|
||||||
|
var record []*BrowseRecord
|
||||||
|
err := adapter.Engine.Table("browse_record").Where("id > ?", last).And("record_type = ?", 1).GroupBy("object_id").Find(&record)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range record {
|
||||||
|
hot := GetBrowseRecordNum(1, v.ObjectId)
|
||||||
|
UpdateNodeHotInfo(v.ObjectId, hot)
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateHotTopic(last int) int {
|
||||||
|
var record []*BrowseRecord
|
||||||
|
err := adapter.Engine.Table("browse_record").Where("id > ? ", last).And("record_type = ?", 2).GroupBy("object_id").Find(&record)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range record {
|
||||||
|
hot := GetBrowseRecordNum(2, v.ObjectId)
|
||||||
|
UpdateTopicHotInfo(v.ObjectId, hot)
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(record)
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/service"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
crawler "github.com/casbin/google-groups-crawler"
|
||||||
|
"github.com/gomarkdown/markdown"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (n Node) AddTopicToMailingList(title, content, author string) {
|
||||||
|
if len(n.MailingList) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content = string(markdown.ToHTML([]byte(content), nil, nil))
|
||||||
|
_ = service.SendEmail(title, content, author, n.MailingList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n Node) SyncFromGoogleGroup() {
|
||||||
|
if !strings.Contains(n.MailingList, "@googlegroups.com") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
topicTitles := n.GetAllTopicTitlesOfNode()
|
||||||
|
|
||||||
|
isInTopicList := func(topicTitle string) bool {
|
||||||
|
for _, title := range topicTitles {
|
||||||
|
if title == topicTitle {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
group := crawler.NewGoogleGroup(n.MailingList, n.GoogleGroupCookie)
|
||||||
|
conversations := group.GetAllConversations(*httpClient)
|
||||||
|
for _, conv := range conversations {
|
||||||
|
messages := conv.GetAllMessages(*httpClient, true)
|
||||||
|
if len(messages) < 1 {
|
||||||
|
fmt.Printf("Google Groups Crawler: Getting messages from Google Group: %s for node: %s failed, please check your cookie.\n", group.GroupName, n.Id)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var newTopic Topic
|
||||||
|
AuthorMember, err := AddMemberByNameAndEmailIfNotExist(messages[0].Author, messages[0].AuthorEmail)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if AuthorMember == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for msgIndex, msgItem := range messages {
|
||||||
|
if msgItem.Files != nil {
|
||||||
|
for _, msgFileItem := range msgItem.Files {
|
||||||
|
messages[msgIndex].Content = fmt.Sprintf("%s<br>[%s](%s)", messages[msgIndex].Content, msgFileItem.FileName, msgFileItem.Url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isInTopicList(conv.Title) {
|
||||||
|
newTopic = Topic{
|
||||||
|
Author: AuthorMember.Id,
|
||||||
|
NodeId: n.Id,
|
||||||
|
NodeName: n.Name,
|
||||||
|
TabId: n.TabId,
|
||||||
|
Title: conv.Title,
|
||||||
|
Content: FilterUnsafeHTML(messages[0].Content),
|
||||||
|
CreatedTime: util.GetTimeFromTimestamp(int64(conv.Time)),
|
||||||
|
LastReplyTime: util.GetTimeFromTimestamp(int64(conv.Time)),
|
||||||
|
EditorType: "richtext",
|
||||||
|
}
|
||||||
|
AddTopic(&newTopic)
|
||||||
|
} else {
|
||||||
|
var topics []Topic
|
||||||
|
err := adapter.Engine.Where("title = ? and deleted = 0", conv.Title).Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if len(topics) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, t := range topics {
|
||||||
|
if conv.Title == t.Title {
|
||||||
|
newTopic = t
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replies := newTopic.GetAllRepliesOfTopic()
|
||||||
|
isInReplies := func(replyStr string) bool {
|
||||||
|
for _, c := range replies {
|
||||||
|
if c == replyStr {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range messages[1:] {
|
||||||
|
msg.Content = FilterUnsafeHTML(msg.Content)
|
||||||
|
AuthorMember, err = AddMemberByNameAndEmailIfNotExist(msg.Author, msg.AuthorEmail)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if AuthorMember == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isInReplies(msg.Content) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newReply := Reply{
|
||||||
|
Author: AuthorMember.Id,
|
||||||
|
TopicId: newTopic.Id,
|
||||||
|
EditorType: "richtext",
|
||||||
|
Content: msg.Content,
|
||||||
|
CreatedTime: util.GetTimeFromTimestamp(int64(msg.Time)),
|
||||||
|
}
|
||||||
|
AddReply(&newReply)
|
||||||
|
newTopic.LastReplyTime = util.GetTimeFromTimestamp(int64(msg.Time))
|
||||||
|
newTopic.LastReplyUser = AuthorMember.Id
|
||||||
|
}
|
||||||
|
UpdateTopic(newTopic.Id, &newTopic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AutoSyncGoogleGroup() {
|
||||||
|
if AutoSyncPeriodSecond < 30 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Duration(AutoSyncPeriodSecond) * time.Second)
|
||||||
|
SyncAllNodeFromGoogleGroup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SyncAllNodeFromGoogleGroup() {
|
||||||
|
if AutoSyncPeriodSecond < 30 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("Sync from google group started...")
|
||||||
|
var nodes []Node
|
||||||
|
err := adapter.Engine.Find(&nodes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
node.SyncFromGoogleGroup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Reply) AddReplyToMailingList() {
|
||||||
|
targetTopic := GetTopic(r.TopicId)
|
||||||
|
targetNode := GetNode(targetTopic.NodeId)
|
||||||
|
if len(targetNode.MailingList) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.EditorType == "markdown" {
|
||||||
|
r.Content = string(markdown.ToHTML([]byte(r.Content), nil, nil))
|
||||||
|
}
|
||||||
|
mailTitle := fmt.Sprintf("Re: %s", targetTopic.Title)
|
||||||
|
_ = service.SendEmail(mailTitle, r.Content, r.Author, targetNode.MailingList)
|
||||||
|
}
|
|
@ -0,0 +1,207 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/casdoor"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetRankingRich() ([]*casdoorsdk.User, error) {
|
||||||
|
return casdoor.GetSortedUsers("score", 25), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRankingPlayer() ([]*casdoorsdk.User, error) {
|
||||||
|
return casdoor.GetSortedUsers("karma", 25), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUser(id string) *casdoorsdk.User {
|
||||||
|
user := casdoor.GetUser(id)
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUsers() []*casdoorsdk.User {
|
||||||
|
users := casdoor.GetUsers()
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMemberNum() int {
|
||||||
|
return casdoor.GetUserCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateMemberEditorType(user *casdoorsdk.User, editorType string) (bool, error) {
|
||||||
|
if user == nil {
|
||||||
|
return false, fmt.Errorf("user is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
SetUserField(user, "editorType", editorType)
|
||||||
|
return casdoorsdk.UpdateUserForColumns(user, []string{"properties"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMemberEditorType(user *casdoorsdk.User) string {
|
||||||
|
return GetUserField(user, "editorType")
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateMemberLanguage(user *casdoorsdk.User, language string) (bool, error) {
|
||||||
|
SetUserField(user, "language", language)
|
||||||
|
return casdoorsdk.UpdateUserForColumns(user, []string{"properties"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMemberLanguage(user *casdoorsdk.User) string {
|
||||||
|
return GetUserField(user, "language")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMemberEmailReminder return member's email reminder status, and his email address.
|
||||||
|
func GetMemberEmailReminder(id string) (bool, string) {
|
||||||
|
user := GetUser(id)
|
||||||
|
if user == nil {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, user.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserByEmail(email string) *casdoorsdk.User {
|
||||||
|
return casdoor.GetUserByEmail(email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMemberCheckinDate(user *casdoorsdk.User) string {
|
||||||
|
return GetUserField(user, "checkinDate")
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateMemberCheckinDate(user *casdoorsdk.User, checkinDate string) (bool, error) {
|
||||||
|
SetUserField(user, "checkinDate", checkinDate)
|
||||||
|
return casdoorsdk.UpdateUserForColumns(user, []string{"properties"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserName(user *casdoorsdk.User) string {
|
||||||
|
if user == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckIsAdmin(user *casdoorsdk.User) bool {
|
||||||
|
if user == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.IsAdmin
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMemberFileQuota(user *casdoorsdk.User) int {
|
||||||
|
if user == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetUserFieldInt(user, "fileQuota")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMemberOnlineStatus updates member's online information.
|
||||||
|
func UpdateMemberOnlineStatus(user *casdoorsdk.User, isOnline bool, lastActionDate string) (bool, error) {
|
||||||
|
if user == nil {
|
||||||
|
return false, fmt.Errorf("user is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
user.IsOnline = isOnline
|
||||||
|
SetUserField(user, "lastActionDate", lastActionDate)
|
||||||
|
return casdoorsdk.UpdateUserForColumns(user, []string{"is_online", "properties"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetOnlineUserCount() int {
|
||||||
|
return casdoor.GetOnlineUserCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateListItem struct {
|
||||||
|
Table string
|
||||||
|
Attribute string
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddMemberByNameAndEmailIfNotExist(username, email string) (*casdoorsdk.User, error) {
|
||||||
|
username = strings.ReplaceAll(username, " ", "")
|
||||||
|
if username == "" {
|
||||||
|
return nil, fmt.Errorf("username is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
email = strings.ReplaceAll(email, " ", "")
|
||||||
|
if email == "" {
|
||||||
|
return nil, fmt.Errorf("email is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := casdoorsdk.GetUser(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if user != nil {
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
username = strings.Split(email, "@")[0]
|
||||||
|
user, err = casdoorsdk.GetUser(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if user != nil {
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newUser := GetUserByEmail(email)
|
||||||
|
if newUser == nil {
|
||||||
|
properties := map[string]string{}
|
||||||
|
properties["emailVerifiedTime"] = util.GetCurrentTime()
|
||||||
|
properties["fileQuota"] = strconv.Itoa(DefaultUploadFileQuota)
|
||||||
|
properties["renameQuota"] = strconv.Itoa(DefaultRenameQuota)
|
||||||
|
|
||||||
|
newUser = &casdoorsdk.User{
|
||||||
|
Name: username,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
UpdatedTime: util.GetCurrentTime(),
|
||||||
|
Id: "",
|
||||||
|
Type: "",
|
||||||
|
Password: "",
|
||||||
|
DisplayName: "",
|
||||||
|
Avatar: "",
|
||||||
|
Email: email,
|
||||||
|
Phone: "",
|
||||||
|
Location: "",
|
||||||
|
Address: nil,
|
||||||
|
Affiliation: "",
|
||||||
|
Title: "",
|
||||||
|
Homepage: "",
|
||||||
|
Tag: "",
|
||||||
|
Score: getInitScore(),
|
||||||
|
Ranking: GetMemberNum() + 1,
|
||||||
|
IsOnline: false,
|
||||||
|
IsAdmin: false,
|
||||||
|
IsGlobalAdmin: false,
|
||||||
|
IsForbidden: false,
|
||||||
|
SignupApplication: CasdoorApplication,
|
||||||
|
Properties: properties,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = casdoorsdk.AddUser(newUser)
|
||||||
|
if err != nil {
|
||||||
|
return newUser, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newUser, nil
|
||||||
|
}
|
|
@ -0,0 +1,357 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Node struct {
|
||||||
|
Id string `xorm:"varchar(100) notnull pk" json:"id"`
|
||||||
|
Name string `xorm:"varchar(100)" json:"name"`
|
||||||
|
CreatedTime string `xorm:"varchar(40)" json:"createdTime"`
|
||||||
|
Desc string `xorm:"mediumtext" json:"desc"`
|
||||||
|
Extra string `xorm:"mediumtext" json:"extra"`
|
||||||
|
Image string `xorm:"varchar(200)" json:"image"`
|
||||||
|
BackgroundImage string `xorm:"varchar(200)" json:"backgroundImage"`
|
||||||
|
HeaderImage string `xorm:"varchar(200)" json:"headerImage"`
|
||||||
|
BackgroundColor string `xorm:"varchar(20)" json:"backgroundColor"`
|
||||||
|
BackgroundRepeat string `xorm:"varchar(20)" json:"backgroundRepeat"`
|
||||||
|
TabId string `xorm:"varchar(100)" json:"tab"`
|
||||||
|
ParentNode string `xorm:"varchar(200)" json:"parentNode"`
|
||||||
|
PlaneId string `xorm:"varchar(50)" json:"planeId"`
|
||||||
|
Sorter int `json:"sorter"`
|
||||||
|
Ranking int `json:"ranking"`
|
||||||
|
Hot int `json:"hot"`
|
||||||
|
Moderators []string `xorm:"varchar(200)" json:"moderators"`
|
||||||
|
MailingList string `xorm:"varchar(100)" json:"mailingList"`
|
||||||
|
GoogleGroupCookie string `xorm:"varchar(1500)" json:"googleGroupCookie"`
|
||||||
|
GitterApiToken string `xorm:"varchar(200)" json:"gitterApiToken"`
|
||||||
|
GitterRoomURL string `xorm:"varchar(200)" json:"gitterRoomUrl"`
|
||||||
|
GitterSyncFromTime string `xorm:"varchar(40)" json:"gitterSyncFromTime"`
|
||||||
|
IsHidden bool `xorm:"bool" json:"isHidden"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNodes() []*Node {
|
||||||
|
nodes := []*Node{}
|
||||||
|
err := adapter.Engine.Desc("sorter").Find(&nodes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNode(id string) *Node {
|
||||||
|
if id == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
node := Node{Id: id}
|
||||||
|
existed, err := adapter.Engine.Get(&node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &node
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateNode(id string, node *Node) bool {
|
||||||
|
if GetNode(id) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := adapter.Engine.Id(id).AllCols().Update(node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
isHidden := "0"
|
||||||
|
if node.IsHidden {
|
||||||
|
isHidden = "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = adapter.Engine.Query("update topic set is_hidden = ? where node_id = ?", isHidden, node.Id)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return affected != 0
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddNode(node *Node) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddNodes(nodes []*Node) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(nodes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteNode(id string) bool {
|
||||||
|
affected, err := adapter.Engine.Id(id).Delete(&Node{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNodesNum() int {
|
||||||
|
node := new(Node)
|
||||||
|
total, err := adapter.Engine.Count(node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNodeTopicNum(id string) int {
|
||||||
|
topic := new(Topic)
|
||||||
|
total, err := adapter.Engine.Where("node_id = ?", id).And("deleted = ?", 0).Count(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNodeFromTab(tab string) []*Node {
|
||||||
|
nodes := []*Node{}
|
||||||
|
err := adapter.Engine.Where("tab_id = ?", tab).Desc("sorter").Find(&nodes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNodeFromPlane(plane string) []*Node {
|
||||||
|
nodes := []*Node{}
|
||||||
|
err := adapter.Engine.Where("plane_id = ?", plane).Cols("id, name").Desc("sorter").Find(&nodes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNodeRelation(id string) *NodeRelation {
|
||||||
|
node := new(Node)
|
||||||
|
parentNode := new(Node)
|
||||||
|
relatedNode := []*Node{}
|
||||||
|
childNode := []*Node{}
|
||||||
|
|
||||||
|
_, err := adapter.Engine.Id(id).Cols("parent_node").Get(node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(3)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
_, err = adapter.Engine.Id(node.ParentNode).Get(parentNode)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
err = adapter.Engine.Table("node").Where("parent_node = ?", node.ParentNode).And("id != ?", node.ParentNode).Find(&relatedNode)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
err = adapter.Engine.Table("node").Where("parent_node = ?", id).And("id != ?", node.ParentNode).Find(&childNode)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
res := &NodeRelation{
|
||||||
|
ParentNode: parentNode,
|
||||||
|
RelatedNode: relatedNode,
|
||||||
|
ChildNode: childNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNodeNavigation() []*NodeNavigationResponse {
|
||||||
|
tabs := GetAllTabs()
|
||||||
|
nodes := GetNodes()
|
||||||
|
|
||||||
|
nodesMap := map[string][]*Node{}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if _, ok := nodesMap[node.TabId]; !ok {
|
||||||
|
nodesMap[node.TabId] = []*Node{}
|
||||||
|
}
|
||||||
|
nodesMap[node.TabId] = append(nodesMap[node.TabId], node)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []*NodeNavigationResponse{}
|
||||||
|
for _, tab := range tabs {
|
||||||
|
temp := NodeNavigationResponse{
|
||||||
|
Tab: tab,
|
||||||
|
Nodes: nodesMap[tab.Id],
|
||||||
|
}
|
||||||
|
res = append(res, &temp)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLatestNode(limit int) []*Node {
|
||||||
|
nodes := []*Node{}
|
||||||
|
err := adapter.Engine.Asc("created_time").Limit(limit).Find(&nodes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHotNode(limit int) []*Node {
|
||||||
|
nodes := []*Node{}
|
||||||
|
err := adapter.Engine.Desc("hot").Limit(limit).Find(&nodes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateNodeHotInfo(nodeId string, hot int) bool {
|
||||||
|
node := new(Node)
|
||||||
|
|
||||||
|
node.Hot = hot
|
||||||
|
affected, err := adapter.Engine.Id(nodeId).Cols("hot").Update(node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNodeModerators(id string) []string {
|
||||||
|
node := Node{Id: id}
|
||||||
|
existed, err := adapter.Engine.Cols("moderators").Get(&node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return node.Moderators
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckNodeModerator(user *casdoorsdk.User, nodeId string) bool {
|
||||||
|
node := Node{Id: nodeId}
|
||||||
|
existed, err := adapter.Engine.Cols("moderators").Get(&node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existed || len(node.Moderators) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, v := range node.Moderators {
|
||||||
|
if v == GetUserName(user) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddNodeModerators(memberId, nodeId string) bool {
|
||||||
|
node := new(Node)
|
||||||
|
|
||||||
|
moderators := GetNodeModerators(nodeId)
|
||||||
|
for _, v := range moderators {
|
||||||
|
if v == memberId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.Moderators = append(moderators, memberId)
|
||||||
|
affected, err := adapter.Engine.Id(nodeId).Cols("moderators").Update(node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteNodeModerators(memberId, nodeId string) bool {
|
||||||
|
node := new(Node)
|
||||||
|
|
||||||
|
moderators := GetNodeModerators(nodeId)
|
||||||
|
for i, v := range moderators {
|
||||||
|
if v == memberId {
|
||||||
|
moderators = append(moderators[:i], moderators[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.Moderators = moderators
|
||||||
|
affected, err := adapter.Engine.Id(nodeId).Cols("moderators").Update(node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n Node) GetAllTopicTitlesOfNode() []string {
|
||||||
|
var topics []Topic
|
||||||
|
var ret []string
|
||||||
|
err := adapter.Engine.Where("node_id = ? and deleted = 0", n.Id).Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for _, topic := range topics {
|
||||||
|
ret = append(ret, topic.Title)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n Node) GetAllTopicsByNode() []Topic {
|
||||||
|
var topics []Topic
|
||||||
|
err := adapter.Engine.Where("node_id = ? and deleted = 0", n.Id).Desc("created_time").Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return topics
|
||||||
|
}
|
|
@ -0,0 +1,320 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/casbin/casnode/service"
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotificationType 1-6 means: reply(topic), mentioned(reply), mentioned(topic), favorite(topic), thanks(topic), thanks(reply)
|
||||||
|
// Status 1-3 means: unread, have read, deleted
|
||||||
|
type Notification struct {
|
||||||
|
Id int `xorm:"int notnull pk autoincr" json:"id"`
|
||||||
|
NotificationType int `xorm:"int index" json:"notificationType"`
|
||||||
|
ObjectId int `xorm:"int index" json:"objectId"`
|
||||||
|
CreatedTime string `xorm:"varchar(40)" json:"createdTime"`
|
||||||
|
SenderId string `xorm:"varchar(100)" json:"senderId"`
|
||||||
|
ReceiverId string `xorm:"varchar(100) index" json:"receiverId"`
|
||||||
|
Status int `xorm:"tinyint" json:"-"`
|
||||||
|
// Deleted bool `xorm:"bool" json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddNotification(notification *Notification) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(notification)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteNotification(id string) bool {
|
||||||
|
notification := new(Notification)
|
||||||
|
notification.Status = 3
|
||||||
|
affected, err := adapter.Engine.Id(id).Update(notification)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNotificationCount() int {
|
||||||
|
count, err := adapter.Engine.Count(&Notification{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNotifications(memberId string, limit int, offset int) []*NotificationResponse {
|
||||||
|
notifications := []*NotificationResponse{}
|
||||||
|
err := adapter.Engine.Table("notification").
|
||||||
|
Where("notification.receiver_id = ?", memberId).And("notification.status != ?", 3).
|
||||||
|
Desc("notification.created_time").
|
||||||
|
Cols("notification.*").
|
||||||
|
Limit(limit, offset).Find(¬ifications)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, notification := range notifications {
|
||||||
|
notification.Avatar = getUserAvatar(notification.Notification.SenderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
errChan := make(chan error, 10)
|
||||||
|
res := make([]*NotificationResponse, len(notifications))
|
||||||
|
for k, v := range notifications {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(k int, v *NotificationResponse) {
|
||||||
|
defer wg.Done()
|
||||||
|
switch v.NotificationType {
|
||||||
|
case 1:
|
||||||
|
fallthrough
|
||||||
|
case 2:
|
||||||
|
fallthrough
|
||||||
|
case 6:
|
||||||
|
replyInfo := GetReply(v.ObjectId)
|
||||||
|
if replyInfo == nil || replyInfo.Deleted {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
v.Title = GetReplyTopicTitle(replyInfo.TopicId)
|
||||||
|
v.Content = replyInfo.Content
|
||||||
|
v.ObjectId = replyInfo.TopicId
|
||||||
|
case 3:
|
||||||
|
v.Title = GetTopicTitle(v.ObjectId)
|
||||||
|
case 4:
|
||||||
|
v.Title = GetTopicTitle(v.ObjectId)
|
||||||
|
case 5:
|
||||||
|
v.Title = GetTopicTitle(v.ObjectId)
|
||||||
|
}
|
||||||
|
res[k] = v
|
||||||
|
}(k, v)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
close(errChan)
|
||||||
|
if len(errChan) != 0 {
|
||||||
|
for v := range errChan {
|
||||||
|
panic(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNotificationNum(memberId string) int {
|
||||||
|
var total int64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
notification := new(Notification)
|
||||||
|
total, err = adapter.Engine.Where("receiver_id = ?", memberId).And("status != ?", 3).Count(notification)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUnreadNotificationNum(memberId string) int {
|
||||||
|
var total int64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
notification := new(Notification)
|
||||||
|
total, err = adapter.Engine.Where("receiver_id = ?", memberId).And("status = ?", 1).Count(notification)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func GetNotificationId() int {
|
||||||
|
num := GetNotificationCount()
|
||||||
|
|
||||||
|
res := num + 1
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func UpdateReadStatus(id string) bool {
|
||||||
|
notification := new(Notification)
|
||||||
|
notification.Status = 2
|
||||||
|
affected, err := adapter.Engine.Where("receiver_id = ?", id).Cols("status").Update(notification)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddReplyNotification(senderId, content string, objectId, topicId int) {
|
||||||
|
memberMap := make(map[string]bool)
|
||||||
|
|
||||||
|
topicInfo := GetTopicBasicInfo(topicId)
|
||||||
|
receiverId := topicInfo.Author
|
||||||
|
memberMap[receiverId] = true
|
||||||
|
|
||||||
|
reg := regexp.MustCompile("@(.*?)[ \n\t]")
|
||||||
|
reg2 := regexp.MustCompile("@([^ \n\t]*?)[^ \n\t]$")
|
||||||
|
regResult := reg.FindAllStringSubmatch(content, -1)
|
||||||
|
regResult2 := reg2.FindAllStringSubmatch(content, -1)
|
||||||
|
|
||||||
|
for _, v := range regResult {
|
||||||
|
if senderId != v[1] && !memberMap[v[1]] {
|
||||||
|
memberMap[v[1]] = true
|
||||||
|
AddMemberFavorites(v[1], "subscribe_topic", strconv.Itoa(topicId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range regResult2 {
|
||||||
|
v[1] += content[len(content)-1:]
|
||||||
|
if senderId != v[1] && !memberMap[v[1]] {
|
||||||
|
memberMap[v[1]] = true
|
||||||
|
AddMemberFavorites(v[1], "subscribe_topic", strconv.Itoa(topicId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeUsers := GetMembersFromFavorites(strconv.Itoa(topicId), SubscribeTopic)
|
||||||
|
for _, v := range subscribeUsers {
|
||||||
|
if senderId != v.Name && !memberMap[v.Name] {
|
||||||
|
memberMap[v.Name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
if senderId != receiverId {
|
||||||
|
notification := Notification{
|
||||||
|
// Id: memberMap[receiverId],
|
||||||
|
NotificationType: 1,
|
||||||
|
ObjectId: objectId,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
SenderId: senderId,
|
||||||
|
ReceiverId: receiverId,
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
_ = AddNotification(¬ification)
|
||||||
|
// send remind email
|
||||||
|
reminder, email := GetMemberEmailReminder(receiverId)
|
||||||
|
if email != "" && reminder {
|
||||||
|
topicIdStr := util.IntToString(topicId)
|
||||||
|
err := sendRemindMail(topicInfo.Title, content, topicIdStr, senderId, email, Domain)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(memberMap, receiverId)
|
||||||
|
for receiverId2 := range memberMap {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(receiverId2 string) {
|
||||||
|
defer wg.Done()
|
||||||
|
notification := Notification{
|
||||||
|
NotificationType: 2,
|
||||||
|
ObjectId: objectId,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
SenderId: senderId,
|
||||||
|
ReceiverId: receiverId2,
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
_ = AddNotification(¬ification)
|
||||||
|
// send remind email
|
||||||
|
reminder, email := GetMemberEmailReminder(receiverId2)
|
||||||
|
if email != "" && reminder {
|
||||||
|
topicIdStr := util.IntToString(topicId)
|
||||||
|
err := sendRemindMail(topicInfo.Title, content, topicIdStr, senderId, email, Domain)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(receiverId2)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddTopicNotification(objectId int, author, content string) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
memberMap := make(map[string]bool)
|
||||||
|
reg := regexp.MustCompile("@(.*?)[ \n\t]")
|
||||||
|
reg2 := regexp.MustCompile("@([^ \n\t]*?)[^ \n\t]$")
|
||||||
|
regResult := reg.FindAllStringSubmatch(content, -1)
|
||||||
|
regResult2 := reg2.FindAllStringSubmatch(content, -1)
|
||||||
|
|
||||||
|
for _, v := range regResult {
|
||||||
|
if author != v[1] && !memberMap[v[1]] {
|
||||||
|
memberMap[v[1]] = true
|
||||||
|
AddMemberFavorites(v[1], "subscribe_topic", strconv.Itoa(objectId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range regResult2 {
|
||||||
|
v[1] += content[len(content)-1:]
|
||||||
|
if author != v[1] && !memberMap[v[1]] {
|
||||||
|
memberMap[v[1]] = true
|
||||||
|
AddMemberFavorites(v[1], "subscribe_topic", strconv.Itoa(objectId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range memberMap {
|
||||||
|
wg.Add(1)
|
||||||
|
k := k
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
notification := Notification{
|
||||||
|
NotificationType: 3,
|
||||||
|
ObjectId: objectId,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
SenderId: author,
|
||||||
|
ReceiverId: k,
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
_ = AddNotification(¬ification)
|
||||||
|
// send remind email
|
||||||
|
reminder, email := GetMemberEmailReminder(k)
|
||||||
|
if email != "" && reminder {
|
||||||
|
topicIdStr := util.IntToString(objectId)
|
||||||
|
err := sendRemindMail(GetTopicTitle(objectId), content, topicIdStr, author, email, Domain)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendRemindMail(title string, content string, topicId string, sender string, receiver string, domain string) error {
|
||||||
|
fromName := ""
|
||||||
|
conf := GetFrontConfById("forumName")
|
||||||
|
if conf != nil {
|
||||||
|
fromName = conf.Value
|
||||||
|
}
|
||||||
|
if fromName == "" {
|
||||||
|
fromName = beego.AppConfig.String("appname")
|
||||||
|
}
|
||||||
|
|
||||||
|
return service.SendRemindMail(fromName, title, content, topicId, sender, receiver, domain)
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type Plane struct {
|
||||||
|
Id string `xorm:"varchar(50) notnull pk" json:"id"`
|
||||||
|
Name string `xorm:"varchar(50)" json:"name"`
|
||||||
|
Sorter int `xorm:"int" json:"-"`
|
||||||
|
CreatedTime string `xorm:"varchar(40)" json:"createdTime"`
|
||||||
|
Image string `xorm:"varchar(200)" json:"image"`
|
||||||
|
BackgroundColor string `xorm:"varchar(20)" json:"backgroundColor"`
|
||||||
|
Color string `xorm:"varchar(20)" json:"color"`
|
||||||
|
Visible bool `xorm:"bool" json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPlanes() []*Plane {
|
||||||
|
planes := []*Plane{}
|
||||||
|
err := adapter.Engine.Asc("sorter").Where("visible = ?", 1).Find(&planes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return planes
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllPlanes() []*AdminPlaneInfo {
|
||||||
|
planes := []*Plane{}
|
||||||
|
err := adapter.Engine.Asc("sorter").Find(&planes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []*AdminPlaneInfo{}
|
||||||
|
for _, v := range planes {
|
||||||
|
temp := AdminPlaneInfo{
|
||||||
|
Plane: *v,
|
||||||
|
Sorter: v.Sorter,
|
||||||
|
Visible: v.Visible,
|
||||||
|
NodesNum: GetPlaneNodesNum(v.Id),
|
||||||
|
}
|
||||||
|
res = append(res, &temp)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPlane(id string) *Plane {
|
||||||
|
plane := Plane{Id: id}
|
||||||
|
existed, err := adapter.Engine.Get(&plane)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &plane
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPlaneAdmin(id string) *AdminPlaneInfo {
|
||||||
|
plane := Plane{Id: id}
|
||||||
|
existed, err := adapter.Engine.Get(&plane)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
planeNode := GetNodeFromPlane(plane.Id)
|
||||||
|
res := AdminPlaneInfo{
|
||||||
|
Plane: plane,
|
||||||
|
Sorter: plane.Sorter,
|
||||||
|
Visible: plane.Visible,
|
||||||
|
NodesNum: len(planeNode),
|
||||||
|
Nodes: planeNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &res
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddPlane(plane *Plane) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(plane)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdatePlane(id string, plane *Plane) bool {
|
||||||
|
if GetPlane(id) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.Id(id).AllCols().Update(plane)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPlaneList() []*PlaneWithNodes {
|
||||||
|
planes := GetPlanes()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
res := make([]*PlaneWithNodes, len(planes))
|
||||||
|
for k, plane := range planes {
|
||||||
|
plane := plane
|
||||||
|
k := k
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
temp := &PlaneWithNodes{
|
||||||
|
Plane: plane,
|
||||||
|
Nodes: GetNodeFromPlane(plane.Id),
|
||||||
|
}
|
||||||
|
res[k] = temp
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeletePlane(id string) bool {
|
||||||
|
affected, err := adapter.Engine.Id(id).Delete(&Plane{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPlaneNodesNum(id string) int {
|
||||||
|
node := new(Node)
|
||||||
|
total, err := adapter.Engine.Where("plane_id = ?", id).Count(node)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
type Poster struct {
|
||||||
|
Id string `xorm:"varchar(50) notnull pk" json:"id"`
|
||||||
|
Advertiser string `xorm:"varchar(40)" json:"advertiser"`
|
||||||
|
Link string `xorm:"varchar(500)" json:"link"`
|
||||||
|
PictureLink string `xorm:"varchar(500)" json:"picture_link"`
|
||||||
|
State string `xorm:"varchar(10)" json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddPoster(poster Poster) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(poster)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPoster(id string) *Poster {
|
||||||
|
poster := Poster{Id: id}
|
||||||
|
existed, err := adapter.Engine.Get(&poster)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &poster
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdatePoster(id string, poster Poster) bool {
|
||||||
|
if GetPoster(id) == nil {
|
||||||
|
return AddPoster(poster)
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.Id(id).AllCols().Update(poster)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
var httpClient *http.Client
|
||||||
|
|
||||||
|
func InitHttpClient() {
|
||||||
|
httpClient = getProxyHttpClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAddressOpen(address string) bool {
|
||||||
|
timeout := time.Millisecond * 100
|
||||||
|
conn, err := net.DialTimeout("tcp", address, timeout)
|
||||||
|
if err != nil {
|
||||||
|
// cannot connect to address, proxy is not active
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if conn != nil {
|
||||||
|
defer conn.Close()
|
||||||
|
fmt.Printf("Socks5 proxy enabled: %s\n", address)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProxyHttpClient() *http.Client {
|
||||||
|
httpProxy := beego.AppConfig.String("httpProxy")
|
||||||
|
if httpProxy == "" {
|
||||||
|
return &http.Client{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isAddressOpen(httpProxy) {
|
||||||
|
return &http.Client{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/33585587/creating-a-go-socks5-client
|
||||||
|
dialer, err := proxy.SOCKS5("tcp", httpProxy, nil, proxy.Direct)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := &http.Transport{Dial: dialer.Dial}
|
||||||
|
return &http.Client{
|
||||||
|
Transport: tr,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,510 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Reply struct {
|
||||||
|
Id int `xorm:"int notnull pk autoincr" json:"id"`
|
||||||
|
Author string `xorm:"varchar(100) index" json:"author"`
|
||||||
|
TopicId int `xorm:"int index" json:"topicId"`
|
||||||
|
ParentId int `xorm:"int" json:"parentId"`
|
||||||
|
Tags []string `xorm:"varchar(200)" json:"tags"`
|
||||||
|
CreatedTime string `xorm:"varchar(40)" json:"createdTime"`
|
||||||
|
Deleted bool `xorm:"bool" json:"deleted"`
|
||||||
|
IsHidden bool `xorm:"bool" json:"isHidden"`
|
||||||
|
ThanksNum int `xorm:"int" json:"thanksNum"`
|
||||||
|
EditorType string `xorm:"varchar(40)" json:"editorType"`
|
||||||
|
Content string `xorm:"mediumtext" json:"content"`
|
||||||
|
Ip string `xorm:"varchar(100)" json:"ip"`
|
||||||
|
State string `xorm:"varchar(100)" json:"state"`
|
||||||
|
GitterMessageId string `xorm:"varchar(100)" json:"gitterMessageId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var enableNestedReply, _ = beego.AppConfig.Bool("enableNestedReply")
|
||||||
|
|
||||||
|
// GetReplyCount returns all replies num so far, both deleted and not deleted.
|
||||||
|
func GetReplyCount() int {
|
||||||
|
count, err := adapter.Engine.Count(&Reply{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetReplies
|
||||||
|
// @router /get-replies [get]
|
||||||
|
// @Description GetReplies returns more information about reply of a topic.
|
||||||
|
// @Tag Reply API
|
||||||
|
func GetReplies(topicId int, user *casdoorsdk.User, limit int, page int) ([]*ReplyWithAvatar, int) {
|
||||||
|
replies := []*ReplyWithAvatar{}
|
||||||
|
realPage := page
|
||||||
|
err := adapter.Engine.Table("reply").
|
||||||
|
Join("LEFT OUTER", "consumption_record", "consumption_record.object_id = reply.id and consumption_record.consumption_type = ?", 5).
|
||||||
|
Where("reply.topic_id = ?", topicId).
|
||||||
|
Asc("reply.created_time").
|
||||||
|
Cols("reply.*, consumption_record.amount").
|
||||||
|
Find(&replies)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, reply := range replies {
|
||||||
|
reply.Avatar = getUserAvatar(reply.Author)
|
||||||
|
}
|
||||||
|
|
||||||
|
isAdmin := CheckIsAdmin(user)
|
||||||
|
for _, v := range replies {
|
||||||
|
v.ThanksStatus = v.ConsumptionAmount != 0
|
||||||
|
v.Deletable = isAdmin || ReplyDeletable(v.CreatedTime, GetUserName(user), v.Author)
|
||||||
|
v.Editable = isAdmin || GetReplyEditableStatus(GetUserName(user), v.Author, v.CreatedTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultReplies []*ReplyWithAvatar
|
||||||
|
|
||||||
|
if enableNestedReply {
|
||||||
|
replies = bulidReplies(replies)
|
||||||
|
// Use limit to calculate offset
|
||||||
|
// If limit is 2, but the first reply have 2 child replies(3 replies)
|
||||||
|
// We need put these replies to offset, so cannot use (page * limit) to calculate offset
|
||||||
|
pageLimit := limit
|
||||||
|
for index, reply := range replies {
|
||||||
|
replyLen := getReplyLen(reply)
|
||||||
|
// Ignore replies until page == 1
|
||||||
|
if page > 1 {
|
||||||
|
// Calculate limit in every ignore page
|
||||||
|
pageLimit -= replyLen
|
||||||
|
// Get replices for init == true(get the latest replies)
|
||||||
|
resultReplies = append(resultReplies, reply)
|
||||||
|
if pageLimit <= 0 {
|
||||||
|
page--
|
||||||
|
pageLimit = limit
|
||||||
|
if index+1 < len(replies) {
|
||||||
|
// If the page is a usable value when we get the latest replies, clear the result
|
||||||
|
resultReplies = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if limit > 0 {
|
||||||
|
// if page == 1, prove that we are processing current page now
|
||||||
|
// So we can only calculate the limit and put replies to result slice
|
||||||
|
limit -= replyLen
|
||||||
|
resultReplies = append(resultReplies, reply)
|
||||||
|
page--
|
||||||
|
} else {
|
||||||
|
// if page == 1, and limit < 0, prove that we get all replies in this page now
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if page > 0 {
|
||||||
|
realPage -= page
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offset := page*limit - limit
|
||||||
|
for _, reply := range replies {
|
||||||
|
if offset > 0 {
|
||||||
|
offset--
|
||||||
|
} else {
|
||||||
|
if limit > 0 {
|
||||||
|
resultReplies = append(resultReplies, reply)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultReplies, realPage
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeReplyTree(replies []*ReplyWithAvatar, reply *ReplyWithAvatar) bool {
|
||||||
|
if len(replies) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, r := range replies {
|
||||||
|
if r.Id == reply.ParentId {
|
||||||
|
r.Child = append(r.Child, reply)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
if makeReplyTree(r.Child, reply) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReplyLen(reply *ReplyWithAvatar) int {
|
||||||
|
replyLen := 1
|
||||||
|
for _, child := range reply.Child {
|
||||||
|
replyLen += getReplyLen(child)
|
||||||
|
}
|
||||||
|
return replyLen
|
||||||
|
}
|
||||||
|
|
||||||
|
func bulidReplies(replies []*ReplyWithAvatar) []*ReplyWithAvatar {
|
||||||
|
var childReplies []*ReplyWithAvatar
|
||||||
|
var repliesResult []*ReplyWithAvatar
|
||||||
|
for _, reply := range replies {
|
||||||
|
if reply.ParentId != 0 {
|
||||||
|
childReplies = append(childReplies, reply)
|
||||||
|
} else {
|
||||||
|
repliesResult = append(repliesResult, reply)
|
||||||
|
}
|
||||||
|
if reply.Deleted {
|
||||||
|
reply.Content = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replies = repliesResult
|
||||||
|
|
||||||
|
for _, child := range childReplies {
|
||||||
|
makeReplyTree(replies, child)
|
||||||
|
}
|
||||||
|
return replies
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRepliesOfTopic(topicId int) []Reply {
|
||||||
|
var ret []Reply
|
||||||
|
err := adapter.Engine.Where("topic_id = ?", topicId).And("deleted = ?", 0).Find(&ret)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTopicReplyNum returns topic's reply num.
|
||||||
|
func GetTopicReplyNum(topicId int) int {
|
||||||
|
var total int64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
reply := new(Reply)
|
||||||
|
total, err = adapter.Engine.Where("topic_id = ?", topicId).And("deleted = ?", 0).Count(reply)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReplyByContentAndAuthor returns reply by content and author.
|
||||||
|
func GetReplyByContentAndAuthor(content string, author string) []*Reply {
|
||||||
|
var ret []*Reply
|
||||||
|
err := adapter.Engine.Where("content = ?", content).And("author = ?", author).Find(&ret)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLatestReplyInfo returns topic's latest reply information.
|
||||||
|
func GetLatestReplyInfo(topicId int) *Reply {
|
||||||
|
var reply Reply
|
||||||
|
exist, err := adapter.Engine.Where("topic_id = ?", topicId).And("deleted = ?", false).Desc("created_time").Limit(1).Omit("content").Get(&reply)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exist {
|
||||||
|
return &reply
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReply returns a single reply.
|
||||||
|
func GetReply(id int) *Reply {
|
||||||
|
reply := Reply{Id: id}
|
||||||
|
existed, err := adapter.Engine.Get(&reply)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &reply
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReplyWithDetails returns more information about reply, including avatar, thanks status, deletable and editable.
|
||||||
|
func GetReplyWithDetails(user *casdoorsdk.User, id int) *ReplyWithAvatar {
|
||||||
|
reply := ReplyWithAvatar{}
|
||||||
|
existed, err := adapter.Engine.Table("reply").
|
||||||
|
Join("LEFT OUTER", "consumption_record", "consumption_record.object_id = reply.id and consumption_record.consumption_type = ?", 5).
|
||||||
|
Id(id).Cols("reply.*, consumption_record.amount").Get(&reply)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.Avatar = getUserAvatar(reply.Author)
|
||||||
|
|
||||||
|
isAdmin := CheckIsAdmin(user)
|
||||||
|
if existed {
|
||||||
|
reply.ThanksStatus = reply.ConsumptionAmount != 0
|
||||||
|
reply.Deletable = isAdmin || ReplyDeletable(reply.CreatedTime, GetUserName(user), reply.Author)
|
||||||
|
reply.Editable = isAdmin || GetReplyEditableStatus(GetUserName(user), reply.Author, reply.CreatedTime)
|
||||||
|
return &reply
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func GetReplyId() int {
|
||||||
|
reply := new(Reply)
|
||||||
|
_, err := adapter.Engine.Desc("created_time").Omit("content").Limit(1).Get(reply)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := util.ParseInt(reply.Id) + 1
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// UpdateReply updates reply's all field.
|
||||||
|
func UpdateReply(id int, reply *Reply) bool {
|
||||||
|
if GetReply(id) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
reply.Content = FilterUnsafeHTML(reply.Content)
|
||||||
|
_, err := adapter.Engine.Id(id).AllCols().Update(reply)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return affected != 0
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateReplyWithLimitCols updates reply's not null field.
|
||||||
|
func UpdateReplyWithLimitCols(id int, reply *Reply) bool {
|
||||||
|
if GetReply(id) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
reply.Content = FilterUnsafeHTML(reply.Content)
|
||||||
|
_, err := adapter.Engine.Id(id).Update(reply)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return affected != 0
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddReply returns add reply result and reply id.
|
||||||
|
func AddReply(reply *Reply) (bool, int) {
|
||||||
|
// reply.Content = strings.ReplaceAll(reply.Content, "\n", "<br/>")
|
||||||
|
reply.Content = FilterUnsafeHTML(reply.Content)
|
||||||
|
affected, err := adapter.Engine.Insert(reply)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0, reply.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddReplies(replies []*Reply) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(replies)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddRepliesInBatch(relies []*Reply) bool {
|
||||||
|
batchSize := 1000
|
||||||
|
|
||||||
|
if len(relies) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected := false
|
||||||
|
for i := 0; i < (len(relies)-1)/batchSize+1; i++ {
|
||||||
|
start := i * batchSize
|
||||||
|
end := (i + 1) * batchSize
|
||||||
|
if end > len(relies) {
|
||||||
|
end = len(relies)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := relies[start:end]
|
||||||
|
fmt.Printf("Add relies: [%d - %d].\n", start, end)
|
||||||
|
if AddReplies(tmp) {
|
||||||
|
affected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func DeleteReply(id string) bool {
|
||||||
|
affected, err := adapter.Engine.Id(id).Delete(&Reply{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func DeleteRepliesHardByTopicId(topicId int) bool {
|
||||||
|
affected, err := adapter.Engine.Where("topic_id = ?", topicId).Delete(&Reply{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteReply soft delete reply.
|
||||||
|
func DeleteReply(id int) bool {
|
||||||
|
reply := new(Reply)
|
||||||
|
reply.Deleted = true
|
||||||
|
affected, err := adapter.Engine.Id(id).Cols("deleted").Update(reply)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLatestReplies returns member's latest replies.
|
||||||
|
func GetLatestReplies(author string, limit int, offset int) []*LatestReply {
|
||||||
|
replys := []*LatestReply{}
|
||||||
|
err := adapter.Engine.Table("reply").Join("LEFT OUTER", "topic", "topic.id = reply.topic_id").
|
||||||
|
Where("reply.author = ?", author).And("reply.deleted = ?", 0).
|
||||||
|
Desc("reply.created_time").
|
||||||
|
Cols("reply.content, reply.author, reply.created_time, topic.id, topic.node_id, topic.node_name, topic.title, topic.author").
|
||||||
|
Limit(limit, offset).Find(&replys)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return replys
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMemberRepliesNum returns member's all replies num.
|
||||||
|
func GetMemberRepliesNum(memberId string) int {
|
||||||
|
var total int64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
reply := new(Reply)
|
||||||
|
total, err = adapter.Engine.Where("author = ?", memberId).And("deleted = ?", 0).Count(reply)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReplyTopicTitle only returns reply's topic title.
|
||||||
|
func GetReplyTopicTitle(id int) string {
|
||||||
|
topic := Topic{Id: id}
|
||||||
|
existed, err := adapter.Engine.Cols("title").Get(&topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return topic.Title
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReplyAuthor only returns reply's topic author.
|
||||||
|
func GetReplyAuthor(id int) *casdoorsdk.User {
|
||||||
|
reply := Reply{Id: id}
|
||||||
|
existed, err := adapter.Engine.Cols("author").Get(&reply)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetUser(reply.Author)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddReplyThanksNum updates reply's thanks num.
|
||||||
|
func AddReplyThanksNum(id int) bool {
|
||||||
|
affected, err := adapter.Engine.ID(id).Incr("thanks_num", 1).Update(Reply{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplyDeletable checks whether the reply can be deleted.
|
||||||
|
func ReplyDeletable(date, memberId, author string) bool {
|
||||||
|
if memberId != author {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := time.Parse("2006-01-02T15:04:05+08:00", date)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
h, _ := time.ParseDuration("-1h")
|
||||||
|
t = t.Add(8 * h)
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
if now.Sub(t).Minutes() > ReplyDeletableTime {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReplyEditableStatus checks whether the reply can be edited.
|
||||||
|
func GetReplyEditableStatus(member, author, createdTime string) bool {
|
||||||
|
if member != author {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := time.Parse("2006-01-02T15:04:05+08:00", createdTime)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
h, _ := time.ParseDuration("-1h")
|
||||||
|
t = t.Add(8 * h)
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
if now.Sub(t).Minutes() > ReplyEditableTime {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchReplies(keyword string) []Reply {
|
||||||
|
var ret []Reply
|
||||||
|
keyword = fmt.Sprintf("%%%s%%", keyword)
|
||||||
|
|
||||||
|
err := adapter.Engine.Where("deleted = 0").Where("content like ?", keyword).Find(&ret)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type SensitiveWord struct {
|
||||||
|
Word string `xorm:"varchar(64) notnull"`
|
||||||
|
Id int64
|
||||||
|
}
|
||||||
|
|
||||||
|
var sensitiveWords []SensitiveWord
|
||||||
|
|
||||||
|
func loadSensitiveWords() {
|
||||||
|
if len(sensitiveWords) == 0 {
|
||||||
|
err := adapter.Engine.Desc("word").Find(&sensitiveWords)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddSensitiveWord(word string) {
|
||||||
|
if IsSensitiveWord(word) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err := adapter.Engine.Insert(SensitiveWord{Word: word})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
sensitiveWords = nil
|
||||||
|
err = adapter.Engine.Desc("word").Find(&sensitiveWords)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteSensitiveWord(word string) {
|
||||||
|
_, err := adapter.Engine.Delete(SensitiveWord{Word: word})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
sensitiveWords = nil
|
||||||
|
err = adapter.Engine.Desc("word").Find(&sensitiveWords)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsSensitiveWord(word string) bool {
|
||||||
|
loadSensitiveWords()
|
||||||
|
for _, wordObj := range sensitiveWords {
|
||||||
|
if word == wordObj.Word {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSensitiveWords() []string {
|
||||||
|
loadSensitiveWords()
|
||||||
|
var ret []string
|
||||||
|
for _, wordObj := range sensitiveWords {
|
||||||
|
ret = append(ret, wordObj.Word)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContainsSensitiveWord(str string) bool {
|
||||||
|
loadSensitiveWords()
|
||||||
|
for _, wordObj := range sensitiveWords {
|
||||||
|
if strings.Index(str, wordObj.Word) >= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,193 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
type Tab struct {
|
||||||
|
Id string `xorm:"varchar(100) notnull pk" json:"id"`
|
||||||
|
Name string `xorm:"varchar(100)" json:"name"`
|
||||||
|
Sorter int `json:"sorter"`
|
||||||
|
Ranking int `json:"ranking"`
|
||||||
|
CreatedTime string `xorm:"varchar(40)" json:"-"`
|
||||||
|
DefaultNode string `xorm:"varchar(100)" json:"defaultNode"`
|
||||||
|
HomePage bool `xorm:"bool" json:"-"`
|
||||||
|
Desc string `xorm:"mediumtext" json:"desc"`
|
||||||
|
Extra string `xorm:"mediumtext" json:"extra"`
|
||||||
|
Moderators []string `xorm:"varchar(200)" json:"moderators"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTab(id string) *Tab {
|
||||||
|
tab := Tab{Id: id}
|
||||||
|
existed, err := adapter.Engine.Get(&tab)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &tab
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddTab(tab *Tab) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(tab)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddTabs(tabs []*Tab) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(tabs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateTab(id string, tab *Tab) bool {
|
||||||
|
if GetTab(id) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := adapter.Engine.Id(id).AllCols().Omit("id").Update(tab)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return affected != 0
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteTab(id string) bool {
|
||||||
|
affected, err := adapter.Engine.Id(id).Delete(&Tab{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHomePageTabs() []*Tab {
|
||||||
|
tabs := []*Tab{}
|
||||||
|
err := adapter.Engine.Asc("sorter").Where("home_page = ?", 1).Find(&tabs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tabs
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllTabs() []*Tab {
|
||||||
|
tabs := []*Tab{}
|
||||||
|
err := adapter.Engine.Asc("sorter").Find(&tabs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tabs
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTabAdmin returns more tab information for admin.
|
||||||
|
func GetTabAdmin(id string) *AdminTabInfo {
|
||||||
|
tab := Tab{Id: id}
|
||||||
|
existed, err := adapter.Engine.Get(&tab)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
var topicsNum int
|
||||||
|
nodes := GetNodeFromTab(tab.Id)
|
||||||
|
for _, v := range nodes {
|
||||||
|
topicsNum += GetNodeTopicNum(v.Id)
|
||||||
|
}
|
||||||
|
res := AdminTabInfo{
|
||||||
|
Id: tab.Id,
|
||||||
|
Name: tab.Name,
|
||||||
|
Sorter: tab.Sorter,
|
||||||
|
CreatedTime: tab.CreatedTime,
|
||||||
|
DefaultNode: tab.DefaultNode,
|
||||||
|
HomePage: tab.HomePage,
|
||||||
|
NodesNum: len(nodes),
|
||||||
|
TopicsNum: topicsNum,
|
||||||
|
}
|
||||||
|
return &res
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllTabsAdmin() []*AdminTabInfo {
|
||||||
|
tabs := []*Tab{}
|
||||||
|
err := adapter.Engine.Asc("sorter").Find(&tabs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []*AdminTabInfo{}
|
||||||
|
for _, v := range tabs {
|
||||||
|
var topicsNum int
|
||||||
|
nodes := GetNodeFromTab(v.Id)
|
||||||
|
for _, v := range nodes {
|
||||||
|
topicsNum += GetNodeTopicNum(v.Id)
|
||||||
|
}
|
||||||
|
temp := AdminTabInfo{
|
||||||
|
Id: v.Id,
|
||||||
|
Name: v.Name,
|
||||||
|
Sorter: v.Sorter,
|
||||||
|
CreatedTime: v.CreatedTime,
|
||||||
|
DefaultNode: v.DefaultNode,
|
||||||
|
HomePage: v.HomePage,
|
||||||
|
NodesNum: len(nodes),
|
||||||
|
TopicsNum: topicsNum,
|
||||||
|
}
|
||||||
|
res = append(res, &temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDefaultTab() string {
|
||||||
|
var tab Tab
|
||||||
|
_, err := adapter.Engine.Where("home_page = ?", 1).Asc("sorter").Limit(1).Get(&tab)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tab.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNodesByTab(id string) []*Node {
|
||||||
|
nodes := []*Node{}
|
||||||
|
|
||||||
|
num := HomePageNodeNum
|
||||||
|
|
||||||
|
if id == "all" {
|
||||||
|
err := adapter.Engine.Cols("id, name").Desc("sorter").Limit(num).Find(&nodes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := adapter.Engine.Where("tab_id = ?", id).Cols("id, name").Desc("sorter").Limit(num).Find(&nodes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSyncTabsForTopics(t *testing.T) {
|
||||||
|
InitConfig()
|
||||||
|
InitAdapter()
|
||||||
|
|
||||||
|
nodes := GetNodes()
|
||||||
|
nodeTabMap := map[string]string{}
|
||||||
|
for _, node := range nodes {
|
||||||
|
nodeTabMap[node.Id] = node.TabId
|
||||||
|
}
|
||||||
|
|
||||||
|
topics := GetAllTopics()
|
||||||
|
for i, topic := range topics {
|
||||||
|
tabId := nodeTabMap[topic.NodeId]
|
||||||
|
topic.TabId = tabId
|
||||||
|
affected := updateTopicSimple(topic.Id, topic)
|
||||||
|
if affected == false {
|
||||||
|
panic(fmt.Errorf("TestSyncTabsForTopics() error, affected == false"))
|
||||||
|
}
|
||||||
|
fmt.Printf("[%d/%d]: Synced tab for topic: [%d, %s] as tab: %s\n", i+1, len(topics), topic.Id, topic.Author, topic.TabId)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,806 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/casbin/casnode/util"
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
"github.com/gomarkdown/markdown"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Topic struct {
|
||||||
|
Id int `xorm:"int notnull pk autoincr" json:"id"`
|
||||||
|
Author string `xorm:"varchar(100) index" json:"author"`
|
||||||
|
NodeId string `xorm:"varchar(100) index" json:"nodeId"`
|
||||||
|
NodeName string `xorm:"varchar(100)" json:"nodeName"`
|
||||||
|
TabId string `xorm:"varchar(100) index" json:"tabId"`
|
||||||
|
Title string `xorm:"varchar(300) index" json:"title"`
|
||||||
|
CreatedTime string `xorm:"varchar(40)" json:"createdTime"`
|
||||||
|
Tags []string `xorm:"varchar(200)" json:"tags"`
|
||||||
|
ReplyCount int `json:"replyCount"`
|
||||||
|
UpCount int `json:"upCount"`
|
||||||
|
DownCount int `json:"downCount"`
|
||||||
|
HitCount int `json:"hitCount"`
|
||||||
|
Hot int `xorm:"index" json:"hot"`
|
||||||
|
FavoriteCount int `json:"favoriteCount"`
|
||||||
|
SubscribeCount int `json:"subscribeCount"`
|
||||||
|
HomePageTopTime string `xorm:"varchar(40) index(IDX_topic_htt_lrt)" json:"homePageTopTime"`
|
||||||
|
TabTopTime string `xorm:"varchar(40) index(IDX_topic_ttt_lrt)" json:"tabTopTime"`
|
||||||
|
NodeTopTime string `xorm:"varchar(40) index(IDX_topic_ntt_lrt)" json:"nodeTopTime"`
|
||||||
|
LastReplyUser string `xorm:"varchar(100)" json:"lastReplyUser"`
|
||||||
|
LastReplyTime string `xorm:"varchar(40) index(IDX_topic_htt_lrt) index(IDX_topic_ttt_lrt) index(IDX_topic_ntt_lrt)" json:"lastReplyTime"`
|
||||||
|
Deleted bool `xorm:"bool index" json:"-"`
|
||||||
|
EditorType string `xorm:"varchar(40)" json:"editorType"`
|
||||||
|
Content string `xorm:"mediumtext" json:"content"`
|
||||||
|
UrlPath string `xorm:"varchar(100)" json:"urlPath"`
|
||||||
|
IsHidden bool `xorm:"bool index" json:"isHidden"`
|
||||||
|
Ip string `xorm:"varchar(100)" json:"ip"`
|
||||||
|
State string `xorm:"varchar(100)" json:"state"`
|
||||||
|
GitterMessageId string `xorm:"varchar(100)" json:"gitterMessageId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicCount() int {
|
||||||
|
count, err := adapter.Engine.Count(&Topic{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicNum() int {
|
||||||
|
count, err := adapter.Engine.Where("deleted = ? and is_hidden = ?", 0, 0).Count(&Topic{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCreatedTopicsNum(memberId string) int {
|
||||||
|
topic := new(Topic)
|
||||||
|
total, err := adapter.Engine.Where("author = ? and deleted = ? and is_hidden = ?", memberId, 0, 0).Count(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAvataredTopics(topics []*Topic) []*TopicWithAvatar {
|
||||||
|
res := []*TopicWithAvatar{}
|
||||||
|
for _, topic := range topics {
|
||||||
|
topicWithAvatar := &TopicWithAvatar{
|
||||||
|
Topic: *topic,
|
||||||
|
Avatar: getUserAvatar(topic.Author),
|
||||||
|
}
|
||||||
|
res = append(res, topicWithAvatar)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopics(limit int, offset int) []*TopicWithAvatar {
|
||||||
|
var topics []*Topic
|
||||||
|
err := adapter.Engine.Table("topic").
|
||||||
|
Where("deleted = ?", 0).And("is_hidden = ?", 0).
|
||||||
|
Desc("home_page_top_time", "last_reply_time").
|
||||||
|
Cols("id, author, node_id, node_name, title, created_time, last_reply_user, last_Reply_time, reply_count, favorite_count, deleted, home_page_top_time, tab_top_time, node_top_time").
|
||||||
|
Limit(limit, offset).Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAvataredTopics(topics)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllTopics() []*Topic {
|
||||||
|
var topics []*Topic
|
||||||
|
err := adapter.Engine.Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return topics
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicsByTitleAndAuthor(title string, author string) []*Topic {
|
||||||
|
topics := []*Topic{}
|
||||||
|
err := adapter.Engine.Where("title = ?", title).And("author = ?", author).Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return topics
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTopicsAdmin *sort: 1 means Asc, 2 means Desc, 0 means no effect.
|
||||||
|
func GetTopicsAdmin(usernameSearchKw, titleSearchKw, contentSearchKw, showDeletedTopic, createdTimeSort, lastReplySort, usernameSort, replyCountSort, hotSort, favCountSort string, limit int, offset int) ([]*AdminTopicInfo, int) {
|
||||||
|
topics := []*Topic{}
|
||||||
|
db := adapter.Engine.Table("topic")
|
||||||
|
|
||||||
|
// created time sort
|
||||||
|
switch createdTimeSort {
|
||||||
|
case "1":
|
||||||
|
db = db.Asc("created_time")
|
||||||
|
case "2":
|
||||||
|
db = db.Desc("created_time")
|
||||||
|
}
|
||||||
|
|
||||||
|
// last reply time sort
|
||||||
|
switch lastReplySort {
|
||||||
|
case "1":
|
||||||
|
db = db.Asc("last_reply_time")
|
||||||
|
case "2":
|
||||||
|
db = db.Desc("last_reply_time")
|
||||||
|
}
|
||||||
|
|
||||||
|
// author sort
|
||||||
|
switch usernameSort {
|
||||||
|
case "1":
|
||||||
|
db = db.Asc("author")
|
||||||
|
case "2":
|
||||||
|
db = db.Desc("author")
|
||||||
|
}
|
||||||
|
|
||||||
|
// reply count sort
|
||||||
|
switch replyCountSort {
|
||||||
|
case "1":
|
||||||
|
db = db.Asc("reply_count")
|
||||||
|
case "2":
|
||||||
|
db = db.Desc("reply_count")
|
||||||
|
}
|
||||||
|
|
||||||
|
// hot sort
|
||||||
|
switch hotSort {
|
||||||
|
case "1":
|
||||||
|
db = db.Asc("hot")
|
||||||
|
case "2":
|
||||||
|
db = db.Desc("hot")
|
||||||
|
}
|
||||||
|
|
||||||
|
// favorite count sort
|
||||||
|
switch favCountSort {
|
||||||
|
case "1":
|
||||||
|
db = db.Asc("favorite_count")
|
||||||
|
case "2":
|
||||||
|
db = db.Desc("favorite_count")
|
||||||
|
}
|
||||||
|
|
||||||
|
if usernameSearchKw != "" {
|
||||||
|
unKw := util.SplitWords(usernameSearchKw)
|
||||||
|
for _, v := range unKw {
|
||||||
|
db.Or("author like ?", "%"+v+"%")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if titleSearchKw != "" {
|
||||||
|
tiKw := util.SplitWords(titleSearchKw)
|
||||||
|
for _, v := range tiKw {
|
||||||
|
db.Or("title like ?", "%"+v+"%")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if contentSearchKw != "" {
|
||||||
|
coKw := util.SplitWords(contentSearchKw)
|
||||||
|
for _, v := range coKw {
|
||||||
|
db.Or("content like ?", "%"+v+"%")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if showDeletedTopic == "0" {
|
||||||
|
db = db.Where("deleted = ?", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
num, err := db.Limit(limit, offset).FindAndCount(&topics, &Topic{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []*AdminTopicInfo{}
|
||||||
|
for _, v := range topics {
|
||||||
|
temp := AdminTopicInfo{
|
||||||
|
Topic: *v,
|
||||||
|
Deleted: v.Deleted,
|
||||||
|
}
|
||||||
|
res = append(res, &temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, int(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicWithAvatar(id int, user *casdoorsdk.User) *TopicWithAvatar {
|
||||||
|
topic := TopicWithAvatar{}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
topicObj := GetTopic(id)
|
||||||
|
if topicObj != nil {
|
||||||
|
topic.Topic = *topicObj
|
||||||
|
|
||||||
|
topic.Avatar = getUserAvatar(topic.Author)
|
||||||
|
|
||||||
|
topic.Editable = GetTopicEditableStatus(user, topic.Author, topic.NodeId, topic.CreatedTime)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
name := ""
|
||||||
|
if user != nil {
|
||||||
|
name = GetUserName(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
topic.ThanksStatus = GetThanksStatus(name, id, 4)
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if topic.Author == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &topic
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopic(id int) *Topic {
|
||||||
|
topic := Topic{Id: id}
|
||||||
|
existed, err := adapter.Engine.Get(&topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &topic
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicByUrlPathAndTitle(urlPath, title, nodeId string) *Topic {
|
||||||
|
topic := Topic{UrlPath: urlPath, Title: title, NodeId: nodeId}
|
||||||
|
existed, err := adapter.Engine.Get(&topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &topic
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicBasicInfo(id int) *Topic {
|
||||||
|
topic := Topic{Id: id}
|
||||||
|
existed, err := adapter.Engine.Id(id).Omit("content").Get(&topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &topic
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicAdmin(id int) *AdminTopicInfo {
|
||||||
|
topic := Topic{Id: id}
|
||||||
|
existed, err := adapter.Engine.Get(&topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &AdminTopicInfo{
|
||||||
|
Topic: topic,
|
||||||
|
Deleted: topic.Deleted,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicTitle(id int) string {
|
||||||
|
topic := Topic{Id: id}
|
||||||
|
existed, err := adapter.Engine.Cols("title").Get(&topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return topic.Title
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicAuthor(id int) *casdoorsdk.User {
|
||||||
|
topic := Topic{Id: id}
|
||||||
|
existed, err := adapter.Engine.Cols("author").Get(&topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetUser(topic.Author)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicNodeId(id int) string {
|
||||||
|
topic := Topic{Id: id}
|
||||||
|
existed, err := adapter.Engine.Cols("node_id").Get(&topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return topic.NodeId
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicsByNode(nodeId string, limit int, offset int) []*NodeTopic {
|
||||||
|
topics := []*NodeTopic{}
|
||||||
|
err := adapter.Engine.Table("topic").
|
||||||
|
Where("node_id = ?", nodeId).And("deleted = ?", 0).
|
||||||
|
Desc("node_top_time", "last_reply_time").
|
||||||
|
Cols("id, author, node_id, node_name, title, created_time, last_reply_user, last_Reply_time, reply_count, favorite_count, deleted, home_page_top_time, tab_top_time, node_top_time").
|
||||||
|
Limit(limit, offset).Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, topic := range topics {
|
||||||
|
topic.Avatar = getUserAvatar(topic.Author)
|
||||||
|
|
||||||
|
topic.ContentLength = len(topic.Content)
|
||||||
|
topic.Content = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return topics
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicsByTag(tagId string, limit int, offset int) []*NodeTopic {
|
||||||
|
topics := []*NodeTopic{}
|
||||||
|
tag := fmt.Sprintf("%%%q%%", tagId)
|
||||||
|
err := adapter.Engine.Table("topic").
|
||||||
|
Where("deleted = ?", 0).And("tags LIKE ?", tag).
|
||||||
|
Desc("node_top_time", "last_reply_time").
|
||||||
|
Cols("id, author, node_id, node_name, title, created_time, last_reply_user, last_Reply_time, reply_count, favorite_count, deleted, home_page_top_time, tab_top_time, node_top_time").
|
||||||
|
Limit(limit, offset).Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, topic := range topics {
|
||||||
|
topic.Avatar = getUserAvatar(topic.Author)
|
||||||
|
|
||||||
|
topic.ContentLength = len(topic.Content)
|
||||||
|
topic.Content = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return topics
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateTopic(id int, topic *Topic) bool {
|
||||||
|
if GetTopic(id) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
topic.Content = FilterUnsafeHTML(topic.Content)
|
||||||
|
_, err := adapter.Engine.Id(id).AllCols().Update(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return affected != 0
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTopicSimple(id int, topic *Topic) bool {
|
||||||
|
affected, err := adapter.Engine.Id(id).AllCols().Update(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateTopicWithLimitCols(id int, topic *Topic) bool {
|
||||||
|
if GetTopic(id) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
topic.Content = FilterUnsafeHTML(topic.Content)
|
||||||
|
_, err := adapter.Engine.Id(id).Update(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return affected != 0
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTopic return add topic result and topic id
|
||||||
|
func AddTopic(topic *Topic) (bool, int) {
|
||||||
|
topic.Content = FilterUnsafeHTML(topic.Content)
|
||||||
|
affected, err := adapter.Engine.Insert(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0, topic.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddTopics(topics []*Topic) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddTopicsInBatch(topics []*Topic) bool {
|
||||||
|
batchSize := 1000
|
||||||
|
|
||||||
|
if len(topics) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected := false
|
||||||
|
for i := 0; i < (len(topics)-1)/batchSize+1; i++ {
|
||||||
|
start := i * batchSize
|
||||||
|
end := (i + 1) * batchSize
|
||||||
|
if end > len(topics) {
|
||||||
|
end = len(topics)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := topics[start:end]
|
||||||
|
fmt.Printf("Add topics: [%d - %d].\n", start, end)
|
||||||
|
if AddTopics(tmp) {
|
||||||
|
affected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteTopicHard(id int) bool {
|
||||||
|
affected, err := adapter.Engine.Id(id).Delete(&Topic{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteTopic(id int) bool {
|
||||||
|
t := GetTopic(id)
|
||||||
|
if strings.HasPrefix(t.Content, "URL: ") {
|
||||||
|
return DeleteTopicHard(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
topic := new(Topic)
|
||||||
|
topic.Deleted = true
|
||||||
|
affected, err := adapter.Engine.Id(id).Cols("deleted").Update(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func GetTopicId() int {
|
||||||
|
topic := new(Topic)
|
||||||
|
_, err := adapter.Engine.Desc("created_time").Omit("content").Limit(1).Get(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := util.ParseInt(topic.Id) + 1
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func GetAllCreatedTopics(author string, tab string, limit int, offset int) []*Topic {
|
||||||
|
topics := []*Topic{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Where("author = ?", author).And("deleted = ?", 0).Omit("content").Limit(limit, offset).Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return topics
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddTopicHitCount(topicId int) bool {
|
||||||
|
affected, err := adapter.Engine.ID(topicId).Incr("hit_count", 1).Update(Topic{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeTopicFavoriteCount(topicId int, num int) bool {
|
||||||
|
affected, err := adapter.Engine.ID(topicId).Incr("favorite_count", num).Update(Topic{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeTopicSubscribeCount(topicId int, num int) bool {
|
||||||
|
affected, err := adapter.Engine.ID(topicId).Incr("subscribe_count", num).Update(Topic{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeTopicReplyCount(topicId int, num int) bool {
|
||||||
|
affected, err := adapter.Engine.ID(topicId).Incr("reply_count", num).Update(Topic{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeTopicLastReplyUser(topicId int, memberId string, updateTime string) bool {
|
||||||
|
topic := GetTopic(topicId)
|
||||||
|
if topic == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
topic.LastReplyUser = memberId
|
||||||
|
topic.LastReplyTime = updateTime
|
||||||
|
if len(memberId) == 0 {
|
||||||
|
topic.LastReplyTime = ""
|
||||||
|
}
|
||||||
|
affected, err := adapter.Engine.Id(topicId).Cols("last_reply_user, last_reply_time").Update(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicsWithTab(tab string, limit, offset int) []*TopicWithAvatar {
|
||||||
|
if tab == "all" {
|
||||||
|
topics := GetTopics(limit, offset)
|
||||||
|
return topics
|
||||||
|
} else {
|
||||||
|
topics := []*Topic{}
|
||||||
|
err := adapter.Engine.Table("topic").
|
||||||
|
Where("tab_id = ?", tab).And("deleted = ?", 0).And("is_hidden = ?", 0).
|
||||||
|
Desc("tab_top_time", "last_reply_time").
|
||||||
|
Cols("id, author, node_id, node_name, title, created_time, last_reply_user, last_Reply_time, reply_count, favorite_count, deleted, home_page_top_time, tab_top_time, node_top_time").
|
||||||
|
Limit(limit, offset).Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAvataredTopics(topics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateTopicHotInfo(topicId string, hot int) bool {
|
||||||
|
topic := new(Topic)
|
||||||
|
|
||||||
|
topic.Hot = hot
|
||||||
|
affected, err := adapter.Engine.Id(topicId).Cols("hot").Update(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHotTopic(limit int) []*TopicWithAvatar {
|
||||||
|
var topics []*Topic
|
||||||
|
err := adapter.Engine.Table("topic").
|
||||||
|
Where("deleted = ? ", 0).
|
||||||
|
Desc("hot").
|
||||||
|
Cols("id, author, node_id, node_name, title, created_time, last_reply_user, last_Reply_time, reply_count, favorite_count, deleted, home_page_top_time, tab_top_time, node_top_time").
|
||||||
|
Limit(limit).Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAvataredTopics(topics)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSortedTopics *sort: 1 means Asc, 2 means Desc, 0 means no effect.
|
||||||
|
func GetSortedTopics(lastReplySort, hotSort, favCountSort, createdTimeSort string, limit int, offset int) []*TopicWithAvatar {
|
||||||
|
var topics []*Topic
|
||||||
|
db := adapter.Engine.Table("topic")
|
||||||
|
// last reply time sort
|
||||||
|
switch lastReplySort {
|
||||||
|
case "1":
|
||||||
|
db = db.Asc("last_reply_time")
|
||||||
|
case "2":
|
||||||
|
db = db.Desc("last_reply_time")
|
||||||
|
}
|
||||||
|
|
||||||
|
// hot sort
|
||||||
|
switch hotSort {
|
||||||
|
case "1":
|
||||||
|
db = db.Asc("hot")
|
||||||
|
case "2":
|
||||||
|
db = db.Desc("hot")
|
||||||
|
}
|
||||||
|
|
||||||
|
// favorite count sort
|
||||||
|
switch favCountSort {
|
||||||
|
case "1":
|
||||||
|
db = db.Asc("favorite_count")
|
||||||
|
case "2":
|
||||||
|
db = db.Desc("favorite_count")
|
||||||
|
}
|
||||||
|
|
||||||
|
// created time sort
|
||||||
|
switch createdTimeSort {
|
||||||
|
case "1":
|
||||||
|
db = db.Desc("created_time")
|
||||||
|
case "2":
|
||||||
|
db = db.Desc("created_time")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.
|
||||||
|
Where("deleted = ? and is_hidden <> ?", 0, 1).
|
||||||
|
Limit(limit, offset).Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAvataredTopics(topics)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTopicEditableStatus(user *casdoorsdk.User, author, nodeId, createdTime string) bool {
|
||||||
|
if CheckIsAdmin(user) || CheckNodeModerator(user, nodeId) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if GetUserName(user) != author {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := time.Parse("2006-01-02T15:04:05+08:00", createdTime)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
h, _ := time.ParseDuration("-1h")
|
||||||
|
t = t.Add(8 * h)
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
if now.Sub(t).Minutes() > TopicEditableTime {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeTopicTopExpiredTime changes topic's top expired time.
|
||||||
|
// topType: tab, node or homePage.
|
||||||
|
func ChangeTopicTopExpiredTime(id int, date, topType string) bool {
|
||||||
|
topic := GetTopic(id)
|
||||||
|
if topic == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch topType {
|
||||||
|
case "tab":
|
||||||
|
topic.TabTopTime = date
|
||||||
|
case "node":
|
||||||
|
topic.NodeTopTime = date
|
||||||
|
case "homePage":
|
||||||
|
topic.HomePageTopTime = date
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.Id(id).Cols("tab_top_time, node_top_time, home_page_top_time").Update(topic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpireTopTopic searches and expires expired top topic.
|
||||||
|
func ExpireTopTopic() int {
|
||||||
|
topics := []*Topic{}
|
||||||
|
err := adapter.Engine.Where("tab_top_time != ?", "").Or("node_top_time != ?", "").Or("home_page_top_time != ?", "").Cols("id, tab_top_time, node_top_time, home_page_top_time").Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var num int
|
||||||
|
date := util.GetCurrentTime()
|
||||||
|
for _, v := range topics {
|
||||||
|
if v.TabTopTime <= date {
|
||||||
|
res := ChangeTopicTopExpiredTime(v.Id, "", "tab")
|
||||||
|
if res {
|
||||||
|
num++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v.NodeTopTime <= date {
|
||||||
|
res := ChangeTopicTopExpiredTime(v.Id, "", "node")
|
||||||
|
if res {
|
||||||
|
num++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v.HomePageTopTime <= date {
|
||||||
|
res := ChangeTopicTopExpiredTime(v.Id, "", "homePage")
|
||||||
|
if res {
|
||||||
|
num++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Topic) GetAllRepliesOfTopic() []string {
|
||||||
|
var ret []string
|
||||||
|
var replies []Reply
|
||||||
|
err := adapter.Engine.Where("topic_id = ? and deleted = 0", t.Id).Find(&replies)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var content string
|
||||||
|
for _, reply := range replies {
|
||||||
|
if reply.EditorType == "markdown" {
|
||||||
|
content = string(markdown.ToHTML([]byte(reply.Content), nil, nil))
|
||||||
|
} else {
|
||||||
|
content = reply.Content
|
||||||
|
}
|
||||||
|
ret = append(ret, content)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchTopics(keyword string) []*TopicWithAvatar {
|
||||||
|
topics := []*Topic{}
|
||||||
|
sqlKeyword := fmt.Sprintf("%%%s%%", keyword)
|
||||||
|
|
||||||
|
err := adapter.Engine.Where("deleted = 0").Where("title like ? or content like ?", sqlKeyword, sqlKeyword).Find(&topics)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
topics2 := []*Topic{}
|
||||||
|
for _, topic := range topics {
|
||||||
|
content := RemoveHtmlTags(topic.Content)
|
||||||
|
if !strings.Contains(content, keyword) && !strings.Contains(topic.Title, keyword) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
topics2 = append(topics2, topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAvataredTopics(topics2)
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2022 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSyncTopicReplyCount(t *testing.T) {
|
||||||
|
InitConfig()
|
||||||
|
InitAdapter()
|
||||||
|
|
||||||
|
topics := GetAllTopics()
|
||||||
|
for _, topic := range topics {
|
||||||
|
num := GetTopicReplyNum(topic.Id)
|
||||||
|
if num != topic.ReplyCount {
|
||||||
|
tmp := topic.ReplyCount
|
||||||
|
topic.ReplyCount = num
|
||||||
|
UpdateTopic(topic.Id, topic)
|
||||||
|
fmt.Printf("[update topic:%d]: ReplyCount: %d -> %d\n", topic.Id, tmp, topic.ReplyCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("Synced ReplyCount of all topics!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSyncTopicFavoriteCount(t *testing.T) {
|
||||||
|
InitConfig()
|
||||||
|
InitAdapter()
|
||||||
|
|
||||||
|
topics := GetAllTopics()
|
||||||
|
for _, topic := range topics {
|
||||||
|
num := GetTopicFavoritesNum(strconv.Itoa(topic.Id))
|
||||||
|
if num != topic.FavoriteCount {
|
||||||
|
tmp := topic.FavoriteCount
|
||||||
|
topic.FavoriteCount = num
|
||||||
|
UpdateTopic(topic.Id, topic)
|
||||||
|
fmt.Printf("[update topic:%d]: FavoriteCount: %d -> %d\n", topic.Id, tmp, topic.FavoriteCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("Synced FavoriteCount of all topics!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSyncTopicSubscribeCount(t *testing.T) {
|
||||||
|
InitConfig()
|
||||||
|
InitAdapter()
|
||||||
|
|
||||||
|
Topics := GetAllTopics()
|
||||||
|
for _, topic := range Topics {
|
||||||
|
num := GetTopicSubscribeNum(strconv.Itoa(topic.Id))
|
||||||
|
if num != topic.SubscribeCount {
|
||||||
|
tmp := topic.SubscribeCount
|
||||||
|
topic.SubscribeCount = num
|
||||||
|
UpdateTopic(topic.Id, topic)
|
||||||
|
fmt.Printf("[update topic:%d]: SubscribeCount: %d -> %d\n", topic.Id, tmp, topic.SubscribeCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("Synced SubscribeCount of all topics!")
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Translator struct {
|
||||||
|
Id string `xorm:"varchar(50) notnull pk" json:"id"`
|
||||||
|
Name string `xorm:"varchar(50)" json:"name"`
|
||||||
|
Translator string `xorm:"varchar(50)" json:"translator"`
|
||||||
|
Key string `xorm:"varchar(200)" json:"key"`
|
||||||
|
Enable bool `xorm:"bool" json:"enable"`
|
||||||
|
Visible bool `xorm:"bool" json:"visible"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TranslateData struct {
|
||||||
|
SrcLang string `json:"srcLang"`
|
||||||
|
Target string `json:"target"`
|
||||||
|
ErrMsg string `json:"err_msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GoogleTranslationResult struct {
|
||||||
|
Data struct {
|
||||||
|
Translations []struct {
|
||||||
|
TranslatedText string `json:"translatedText"`
|
||||||
|
DetectedSourceLanguage string `json:"detectedSourceLanguage"`
|
||||||
|
} `json:"translations"`
|
||||||
|
} `json:"data"`
|
||||||
|
Error struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Errors []struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
} `json:"errors"`
|
||||||
|
} `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func StrTranslate(srcStr, targetLang string) *TranslateData {
|
||||||
|
replaceStr := "<code>RplaceWithCasnodeTranslator<code/>"
|
||||||
|
contentReg := regexp.MustCompile(`(?s)\x60{1,3}[^\x60](.*?)\x60{1,3}`)
|
||||||
|
translateReg := regexp.MustCompile(replaceStr)
|
||||||
|
translateData := &TranslateData{}
|
||||||
|
|
||||||
|
translator := GetEnableTranslator()
|
||||||
|
if translator == nil || !translator.Visible {
|
||||||
|
translateData.ErrMsg = "Translate Failed"
|
||||||
|
return translateData
|
||||||
|
}
|
||||||
|
|
||||||
|
codeBlocks := contentReg.FindAllString(srcStr, -1)
|
||||||
|
var cbList []string
|
||||||
|
|
||||||
|
if codeBlocks != nil {
|
||||||
|
for _, cbItem := range codeBlocks {
|
||||||
|
cbList = append(cbList, cbItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
srcStr = contentReg.ReplaceAllString(srcStr, replaceStr)
|
||||||
|
|
||||||
|
params := url.Values{
|
||||||
|
"target": {targetLang},
|
||||||
|
"format": {"text"},
|
||||||
|
"key": {translator.Key},
|
||||||
|
"q": {srcStr},
|
||||||
|
}
|
||||||
|
resp, _ := http.PostForm("https://translation.googleapis.com/language/translate/v2", params)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respByte, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
var translateResp GoogleTranslationResult
|
||||||
|
translateResp.Error.Code = 0
|
||||||
|
|
||||||
|
err := json.Unmarshal(respByte, &translateResp)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
translateStr := translateResp.Data.Translations[0].TranslatedText
|
||||||
|
detectSrcLang := translateResp.Data.Translations[0].DetectedSourceLanguage
|
||||||
|
|
||||||
|
replacedCb := translateReg.FindAllString(translateStr, -1)
|
||||||
|
var replacedCbList []string
|
||||||
|
if replacedCb != nil {
|
||||||
|
for _, replacedCbItem := range replacedCb {
|
||||||
|
replacedCbList = append(replacedCbList, replacedCbItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(replacedCbList) != len(codeBlocks) {
|
||||||
|
translateData.ErrMsg = "Translate Failed"
|
||||||
|
return translateData
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceIndex := 0
|
||||||
|
translateStr = translateReg.ReplaceAllStringFunc(translateStr, func(src string) string {
|
||||||
|
replaceIndex = replaceIndex + 1
|
||||||
|
return cbList[replaceIndex-1]
|
||||||
|
})
|
||||||
|
|
||||||
|
if translateResp.Error.Code != 0 {
|
||||||
|
translateData.ErrMsg = translateResp.Error.Message
|
||||||
|
} else {
|
||||||
|
translateData.SrcLang = detectSrcLang
|
||||||
|
translateData.Target = translateStr
|
||||||
|
}
|
||||||
|
|
||||||
|
return translateData
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddTranslator(translator Translator) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(translator)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTranslator(id string) *[]Translator {
|
||||||
|
translators := []Translator{}
|
||||||
|
var err error
|
||||||
|
if id != "" {
|
||||||
|
err = adapter.Engine.Where("id = ?", id).Find(&translators)
|
||||||
|
} else {
|
||||||
|
err = adapter.Engine.Find(&translators)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &translators
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEnableTranslator() *Translator {
|
||||||
|
var translator Translator
|
||||||
|
resultNum, err := adapter.Engine.Where("enable = ?", true).Get(&translator)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if resultNum {
|
||||||
|
return &translator
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateTranslator(translator Translator) bool {
|
||||||
|
_, err := adapter.Engine.Where("enable = ?", true).Cols("enable").Update(Translator{Enable: false})
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.Id(translator.Id).AllCols().Update(translator)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DelTranslator(id string) bool {
|
||||||
|
affected, err := adapter.Engine.Where("id = ?", id).Delete(&Translator{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import "github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
|
||||||
|
type LatestReply struct {
|
||||||
|
TopicId int `xorm:"id" json:"topicId"`
|
||||||
|
NodeId string `json:"nodeId"`
|
||||||
|
NodeName string `json:"nodeName"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
ReplyContent string `xorm:"content" json:"replyContent"`
|
||||||
|
TopicTitle string `xorm:"title" json:"topicTitle"`
|
||||||
|
ReplyTime string `xorm:"created_time" json:"replyTime"`
|
||||||
|
TopicAuthor string `xorm:"author" json:"topicAuthor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TopicWithAvatar struct {
|
||||||
|
Topic `xorm:"extends"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
ThanksStatus bool `json:"thanksStatus"`
|
||||||
|
Editable bool `json:"editable"`
|
||||||
|
NodeModerator bool `json:"nodeModerator"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeTopic struct {
|
||||||
|
Topic `xorm:"extends"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
ThanksStatus bool `json:"thanksStatus"`
|
||||||
|
Editable bool `json:"editable"`
|
||||||
|
NodeModerator bool `json:"nodeModerator"`
|
||||||
|
ContentLength int `json:"contentLength"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReplyWithAvatar struct {
|
||||||
|
Reply `xorm:"extends"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
ThanksStatus bool `json:"thanksStatus"`
|
||||||
|
Deletable bool `json:"deletable"`
|
||||||
|
Editable bool `json:"editable"`
|
||||||
|
ConsumptionAmount int `xorm:"amount" json:"amount"`
|
||||||
|
Child []*ReplyWithAvatar `json:"child"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeFavoritesRes struct {
|
||||||
|
NodeInfo *Node `json:"nodeInfo"`
|
||||||
|
TopicNum int `json:"topicNum"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommunityHealth struct {
|
||||||
|
Member int `json:"member"`
|
||||||
|
Topic int `json:"topic"`
|
||||||
|
Reply int `json:"reply"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeRelation struct {
|
||||||
|
ParentNode *Node `json:"parentNode"`
|
||||||
|
RelatedNode []*Node `json:"relatedNode"`
|
||||||
|
ChildNode []*Node `json:"childNode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotificationResponse struct {
|
||||||
|
*Notification `xorm:"extends"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeNavigationResponse struct {
|
||||||
|
*Tab
|
||||||
|
Nodes []*Node `json:"nodes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlaneWithNodes struct {
|
||||||
|
*Plane
|
||||||
|
Nodes []*Node `json:"nodes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BalanceResponse struct {
|
||||||
|
Amount int `json:"amount"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Length int `json:"length"`
|
||||||
|
Balance int `json:"balance"`
|
||||||
|
ObjectId int `json:"objectId"`
|
||||||
|
ReceiverId string `json:"receiverId"`
|
||||||
|
ConsumerId string `json:"consumerId"`
|
||||||
|
CreatedTime string `json:"createdTime"`
|
||||||
|
ConsumptionType int `json:"consumptionType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminTabInfo struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Sorter int `json:"sorter"`
|
||||||
|
CreatedTime string `json:"createdTime"`
|
||||||
|
DefaultNode string `json:"defaultNode"`
|
||||||
|
HomePage bool `json:"homePage"`
|
||||||
|
NodesNum int `json:"nodesNum"`
|
||||||
|
TopicsNum int `json:"topicsNum"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminMemberInfo struct {
|
||||||
|
casdoorsdk.User
|
||||||
|
FileQuota int `json:"fileQuota"`
|
||||||
|
FileUploadNum int `json:"fileUploadNum"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
TopicNum int `json:"topicNum"`
|
||||||
|
ReplyNum int `json:"replyNum"`
|
||||||
|
LatestLogin string `json:"latestLogin"`
|
||||||
|
Score int `json:"score"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminPlaneInfo struct {
|
||||||
|
Plane
|
||||||
|
Sorter int `json:"sorter"`
|
||||||
|
Visible bool `json:"visible"`
|
||||||
|
NodesNum int `json:"nodesNum"`
|
||||||
|
Nodes []*Node `json:"nodes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminTopicInfo struct {
|
||||||
|
Topic
|
||||||
|
Deleted bool `json:"deleted"`
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetUserField(user *casdoorsdk.User, field string) string {
|
||||||
|
return user.Properties[field]
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserFieldInt(user *casdoorsdk.User, field string) int {
|
||||||
|
res, err := strconv.Atoi(user.Properties[field])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetUserField(user *casdoorsdk.User, field string, value string) {
|
||||||
|
if user.Properties == nil {
|
||||||
|
user.Properties = map[string]string{}
|
||||||
|
}
|
||||||
|
user.Properties[field] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInitScore() int {
|
||||||
|
score, err := strconv.Atoi(beego.AppConfig.String("initScore"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return score
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/context"
|
||||||
|
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AutoSigninFilter(ctx *context.Context) {
|
||||||
|
//if getSessionUser(ctx) != "" {
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
|
||||||
|
// "/page?access_token=123"
|
||||||
|
accessToken := ctx.Input.Query("accessToken")
|
||||||
|
if accessToken == "signout" {
|
||||||
|
// sign out
|
||||||
|
setSessionClaims(ctx, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if accessToken != "" {
|
||||||
|
claims, err := casdoorsdk.ParseJwtToken(accessToken)
|
||||||
|
if err != nil {
|
||||||
|
responseError(ctx, "invalid JWT token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims.AccessToken = accessToken
|
||||||
|
setSessionClaims(ctx, claims)
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue