Skip to content

Commit

Permalink
Support user uploading via xlsx.
Browse files Browse the repository at this point in the history
  • Loading branch information
hsluoyz committed Dec 31, 2021
1 parent 5e8897e commit 4b65320
Show file tree
Hide file tree
Showing 17 changed files with 334 additions and 3 deletions.
60 changes: 60 additions & 0 deletions controllers/user_upload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// 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 (
"fmt"
"io"
"mime/multipart"
"os"

"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
)

func saveFile(path string, file *multipart.File) {
f, err := os.Create(path)
if err != nil {
panic(err)
}
defer f.Close()

_, err = io.Copy(f, *file)
if err != nil {
panic(err)
}
}

func (c *ApiController) UploadUsers() {
userId := c.GetSessionUsername()
owner, user := util.GetOwnerAndNameFromId(userId)

file, header, err := c.Ctx.Request.FormFile("file")
if err != nil {
panic(err)
}
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))

path := util.GetUploadXlsxPath(fileId)
util.EnsureFileFolderExists(path)
saveFile(path, &file)

affected := object.UploadUsers(owner, fileId)
if affected {
c.ResponseOk()
} else {
c.ResponseError("Failed to import users")
}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ require (
github.com/russellhaering/goxmldsig v1.1.1
github.com/satori/go.uuid v1.2.0 // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/tealeg/xlsx v1.0.5
github.com/thanhpk/randstr v1.0.4
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2K
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154 h1:THBgwGwUQtsw6L53cSSA2wwL3sLrm+HJ3Dk+ye/lMCI=
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI=
github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo=
Expand Down
114 changes: 114 additions & 0 deletions object/user_upload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// 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 (
"github.com/casbin/casdoor/util"
"github.com/casbin/casdoor/xlsx"
)

func getUserMap(owner string) map[string]*User {
m := map[string]*User{}

users := GetUsers(owner)
for _, user := range users {
m[user.GetId()] = user
}

return m
}

func parseLineItem(line *[]string, i int) string {
if i >= len(*line) {
return ""
} else {
return (*line)[i]
}
}

func parseLineItemInt(line *[]string, i int) int {
s := parseLineItem(line, i)
return util.ParseInt(s)
}

func parseLineItemBool(line *[]string, i int) bool {
return parseLineItemInt(line, i) != 0
}

func UploadUsers(owner string, fileId string) bool {
table := xlsx.ReadXlsxFile(fileId)

oldUserMap := getUserMap(owner)
newUsers := []*User{}
for _, line := range table {
if parseLineItem(&line, 0) == "" {
continue
}

user := &User{
Owner: parseLineItem(&line, 0),
Name: parseLineItem(&line, 1),
CreatedTime: parseLineItem(&line, 2),
UpdatedTime: parseLineItem(&line, 3),
Id: parseLineItem(&line, 4),
Type: parseLineItem(&line, 5),
Password: parseLineItem(&line, 6),
PasswordSalt: parseLineItem(&line, 7),
DisplayName: parseLineItem(&line, 8),
Avatar: parseLineItem(&line, 9),
PermanentAvatar: "",
Email: parseLineItem(&line, 10),
Phone: parseLineItem(&line, 11),
Location: parseLineItem(&line, 12),
Address: []string{parseLineItem(&line, 13)},
Affiliation: parseLineItem(&line, 14),
Title: parseLineItem(&line, 15),
IdCardType: parseLineItem(&line, 16),
IdCard: parseLineItem(&line, 17),
Homepage: parseLineItem(&line, 18),
Bio: parseLineItem(&line, 19),
Tag: parseLineItem(&line, 20),
Region: parseLineItem(&line, 21),
Language: parseLineItem(&line, 22),
Gender: parseLineItem(&line, 23),
Birthday: parseLineItem(&line, 24),
Education: parseLineItem(&line, 25),
Score: parseLineItemInt(&line, 26),
Ranking: parseLineItemInt(&line, 27),
IsDefaultAvatar: false,
IsOnline: parseLineItemBool(&line, 28),
IsAdmin: parseLineItemBool(&line, 29),
IsGlobalAdmin: parseLineItemBool(&line, 30),
IsForbidden: parseLineItemBool(&line, 31),
IsDeleted: parseLineItemBool(&line, 32),
SignupApplication: parseLineItem(&line, 33),
Hash: "",
PreHash: "",
CreatedIp: parseLineItem(&line, 34),
LastSigninTime: parseLineItem(&line, 35),
LastSigninIp: parseLineItem(&line, 36),
Properties: map[string]string{},
}

if _, ok := oldUserMap[user.GetId()]; !ok {
newUsers = append(newUsers, user)
}
}

if len(newUsers) == 0 {
return false
}
return AddUsersInBatch(newUsers)
}
1 change: 1 addition & 0 deletions routers/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func initAPI() {
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
beego.Router("/api/upload-users", &controllers.ApiController{}, "POST:UploadUsers")

beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
Expand Down
19 changes: 19 additions & 0 deletions util/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
)

Expand All @@ -28,6 +29,24 @@ func FileExist(path string) bool {
return true
}

func GetPath(path string) string {
return filepath.Dir(path)
}

func EnsureFileFolderExists(path string) {
p := GetPath(path)
if !FileExist(p) {
err := os.MkdirAll(p, os.ModePerm)
if err != nil {
panic(err)
}
}
}

func RemoveExt(filename string) string {
return filename[:len(filename)-len(filepath.Ext(filename))]
}

func UrlJoin(base string, path string) string {
res := fmt.Sprintf("%s/%s", strings.TrimRight(base, "/"), strings.TrimLeft(path, "/"))
return res
Expand Down
21 changes: 21 additions & 0 deletions util/setting.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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 util

import "fmt"

func GetUploadXlsxPath(fileId string) string {
return fmt.Sprintf("tmpFiles/%s.xlsx", fileId)
}
47 changes: 44 additions & 3 deletions web/src/UserListPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

import React from "react";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Switch, Table} from 'antd';
import {Button, Popconfirm, Switch, Table, Upload} from 'antd';
import {UploadOutlined} from "@ant-design/icons";
import moment from "moment";
import * as Setting from "./Setting";
import * as UserBackend from "./backend/UserBackend";
Expand Down Expand Up @@ -92,6 +93,43 @@ class UserListPage extends BaseListPage {
});
}

uploadFile(info) {
const { status, response: res } = info.file;
if (status === 'done') {
if (res.status === 'ok') {
Setting.showMessage("success", `Users uploaded successfully, refreshing the page`);

const { pagination } = this.state;
this.fetch({ pagination });
} else {
Setting.showMessage("error", `Users failed to upload: ${res.msg}`);
}
} else if (status === 'error') {
Setting.showMessage("error", `File failed to upload`);
}
}

renderUpload() {
const props = {
name: 'file',
accept: '.xlsx',
method: 'post',
action: `${Setting.ServerUrl}/api/upload-users`,
withCredentials: true,
onChange: (info) => {
this.uploadFile(info);
},
};

return (
<Upload {...props}>
<Button type="primary" size="small">
<UploadOutlined /> {i18next.t("user:Upload (.xlsx)")}
</Button>
</Upload>
)
}

renderTable(users) {
// transfer country code to name based on selected language
var countries = require("i18n-iso-countries");
Expand Down Expand Up @@ -316,8 +354,11 @@ class UserListPage extends BaseListPage {
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={users} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Users")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addUser.bind(this)}>{i18next.t("general:Add")}</Button>
{i18next.t("general:Users")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={this.addUser.bind(this)}>{i18next.t("general:Add")}</Button>
{
this.renderUpload()
}
</div>
)}
loading={this.state.loading}
Expand Down
1 change: 1 addition & 0 deletions web/src/locales/de/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@
"Title - Tooltip": "Title - Tooltip",
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
"Unlink": "Unlink",
"Upload (.xlsx)": "Upload (.xlsx)",
"Upload a photo": "Upload a photo",
"input password": "input password"
},
Expand Down
1 change: 1 addition & 0 deletions web/src/locales/en/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@
"Title - Tooltip": "Title - Tooltip",
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
"Unlink": "Unlink",
"Upload (.xlsx)": "Upload (.xlsx)",
"Upload a photo": "Upload a photo",
"input password": "input password"
},
Expand Down
1 change: 1 addition & 0 deletions web/src/locales/fr/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@
"Title - Tooltip": "Title - Tooltip",
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
"Unlink": "Unlink",
"Upload (.xlsx)": "Upload (.xlsx)",
"Upload a photo": "Upload a photo",
"input password": "input password"
},
Expand Down
1 change: 1 addition & 0 deletions web/src/locales/ja/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@
"Title - Tooltip": "Title - Tooltip",
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
"Unlink": "Unlink",
"Upload (.xlsx)": "Upload (.xlsx)",
"Upload a photo": "Upload a photo",
"input password": "input password"
},
Expand Down
1 change: 1 addition & 0 deletions web/src/locales/ko/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@
"Title - Tooltip": "Title - Tooltip",
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
"Unlink": "Unlink",
"Upload (.xlsx)": "Upload (.xlsx)",
"Upload a photo": "Upload a photo",
"input password": "input password"
},
Expand Down
1 change: 1 addition & 0 deletions web/src/locales/ru/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@
"Title - Tooltip": "Title - Tooltip",
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
"Unlink": "Unlink",
"Upload (.xlsx)": "Upload (.xlsx)",
"Upload a photo": "Upload a photo",
"input password": "input password"
},
Expand Down
1 change: 1 addition & 0 deletions web/src/locales/zh/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@
"Title - Tooltip": "在单位/公司的职务",
"Two passwords you typed do not match.": "两次输入的密码不匹配。",
"Unlink": "解绑",
"Upload (.xlsx)": "上传(.xlsx)",
"Upload a photo": "上传头像",
"input password": "输入密码"
},
Expand Down
Loading

0 comments on commit 4b65320

Please sign in to comment.