feat: support storage provider to terms of use file (#221)

Signed-off-by: WindSpiritSR <simon343riley@gmail.com>
This commit is contained in:
WindSpiritSR 2021-08-10 10:43:33 +08:00 committed by Yang Luo
parent 718fc4df74
commit 8c6f0a31b6
9 changed files with 113 additions and 55 deletions

View File

@ -15,7 +15,6 @@
package controllers
import (
"encoding/base64"
"encoding/json"
"fmt"
"strconv"
@ -236,26 +235,38 @@ func (c *ApiController) UploadFile() {
c.ResponseError("No storage provider is found")
return
}
fileString := c.Ctx.Request.Form.Get("file")
var fileBytes []byte
pngHeader := "data:image/png;base64,"
if strings.HasPrefix(fileString, pngHeader) {
fileBytes, _ = base64.StdEncoding.DecodeString(fileString[len(pngHeader):])
} else {
fileBytes = []byte(fileString)
file, header, err := c.GetFile("file")
defer file.Close()
if err != nil {
c.ResponseError("Missing parameter")
return
}
fileUrl, err := object.UploadFile(provider, folder, subFolder, fileBytes)
fileType := header.Header.Get("Content-Type")
fileSuffix := ""
switch fileType {
case "image/png":
fileSuffix = "png"
case "text/html":
fileSuffix = "html"
}
fileUrl, err := object.UploadFile(provider, folder, subFolder, file, fileSuffix)
if err != nil {
c.ResponseError(err.Error())
return
}
if folder == "avatar" {
switch folder {
case "avatar":
user.Avatar = fileUrl
object.UpdateUser(user.GetId(), user)
case "termsofuse":
appId := fmt.Sprintf("admin/%s", strings.Split(subFolder, "/")[0])
app := object.GetApplication(appId)
app.TermsOfUse = fileUrl
object.UpdateApplication(appId, app)
}
c.ResponseOk(fileUrl)

View File

@ -16,7 +16,6 @@ package controllers
import (
"encoding/json"
"github.com/casbin/casdoor/object"
)

View File

@ -17,13 +17,15 @@ package object
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"strings"
"github.com/casbin/casdoor/storage"
"github.com/casbin/casdoor/util"
)
func UploadFile(provider *Provider, folder string, subFolder string, fileBytes []byte) (string, error) {
func UploadFile(provider *Provider, folder string, subFolder string, file multipart.File, suffix string) (string, error) {
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint)
if storageProvider == nil {
return "", fmt.Errorf("the provider type: %s is not supported", provider.Type)
@ -34,8 +36,12 @@ func UploadFile(provider *Provider, folder string, subFolder string, fileBytes [
UpdateProvider(provider.GetId(), provider)
}
path := fmt.Sprintf("%s/%s.png", util.UrlJoin(util.GetUrlPath(provider.Domain), folder), subFolder)
_, err := storageProvider.Put(path, bytes.NewReader(fileBytes))
path := fmt.Sprintf("%s/%s.%s", util.UrlJoin(util.GetUrlPath(provider.Domain), folder), subFolder, suffix)
fileBuf := bytes.NewBuffer(nil)
if _, err := io.Copy(fileBuf, file); err != nil {
return "", err
}
_, err := storageProvider.Put(path, fileBuf)
if err != nil {
return "", err
}

View File

@ -13,12 +13,13 @@
// limitations under the License.
import React from "react";
import {Button, Card, Col, Input, Row, Select, Switch} from 'antd';
import {LinkOutlined} from "@ant-design/icons";
import {Button, Card, Col, Input, Row, Select, Switch, Upload} from 'antd';
import {LinkOutlined, UploadOutlined} from "@ant-design/icons";
import * as ApplicationBackend from "./backend/ApplicationBackend";
import * as Setting from "./Setting";
import * as ProviderBackend from "./backend/ProviderBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as UserBackend from "./backend/UserBackend";
import SignupPage from "./auth/SignupPage";
import LoginPage from "./auth/LoginPage";
import i18next from "i18next";
@ -26,8 +27,6 @@ import UrlTable from "./UrlTable";
import ProviderTable from "./ProviderTable";
import SignupTable from "./SignupTable";
import PromptPage from "./auth/PromptPage";
const { TextArea } = Input;
const { Option } = Select;
class ApplicationEditPage extends React.Component {
@ -39,6 +38,7 @@ class ApplicationEditPage extends React.Component {
application: null,
organizations: [],
providers: [],
uploading: false,
};
}
@ -92,6 +92,25 @@ class ApplicationEditPage extends React.Component {
});
}
handleUpload(info) {
if (info.file.type !== "text/html") {
Setting.showMessage("error", i18next.t("provider:Please select a HTML file"))
return
}
this.setState({uploading: true})
UserBackend.uploadFile("termsofuse", `${this.state.applicationName}/termsofuse`, info.file)
.then(res => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Upload success"))
this.updateApplicationField("termsOfUse", res.data)
} else {
Setting.showMessage("error", res.msg)
}
}).finally(() => {
this.setState({uploading: false})
})
}
renderApplication() {
return (
<Card size="small" title={
@ -286,9 +305,13 @@ class ApplicationEditPage extends React.Component {
{Setting.getLabel(i18next.t("provider:Terms of Use"), i18next.t("provider:Terms of Use - Tooltip"))} :
</Col>
<Col span={22} >
<TextArea autoSize={{minRows: 1, maxRows: 6}} value={this.state.application.termsOfUse} onChange={e => {
this.updateApplicationField('termsOfUse', e.target.value);
}} />
<Input value={this.state.application.termsOfUse} style={{marginBottom: "10px"}} onChange={e => {
this.updateApplicationField("termsOfUse", e.target.value)
}}/>
<Upload maxCount={1} accept=".html" showUploadList={false}
beforeUpload={file => {return false}} onChange={info => {this.handleUpload(info)}}>
<Button icon={<UploadOutlined />} loading={this.state.uploading}>Click to Upload</Button>
</Upload>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >

View File

@ -49,15 +49,23 @@ export const CropperDiv = (props) => {
};
const uploadAvatar = () => {
let canvas = cropper.getCroppedCanvas();
if (canvas === null) {
Setting.showMessage("error", "You must select a picture first!");
return false;
}
// Setting.showMessage("success", "uploading...");
const userId = `${user.owner}/${user.name}`;
UserBackend.uploadFile("avatar", userId, canvas.toDataURL());
return true;
cropper.getCroppedCanvas().toBlob(blob => {
if (blob === null) {
Setting.showMessage("error", "You must select a picture first!");
return false;
}
// Setting.showMessage("success", "uploading...");
const userId = `${user.owner}/${user.name}`;
UserBackend.uploadFile("avatar", userId, blob)
.then((res) => {
if (res.status === "ok") {
window.location.href = "/account";
} else {
Setting.showMessage("error", res.msg);
}
});
return true;
});
}
const showModal = () => {

View File

@ -72,6 +72,7 @@ class SignupPage extends React.Component {
validPhone: false,
region: "",
isTermsOfUseVisible: false,
termsOfUseContent: "",
};
this.form = React.createRef();
@ -95,6 +96,7 @@ class SignupPage extends React.Component {
this.setState({
application: application,
});
this.getTermsofuseContent(application.termsOfUse);
});
}
@ -118,6 +120,16 @@ class SignupPage extends React.Component {
}
}
getTermsofuseContent(url) {
fetch(url, {
method: "GET",
}).then(r => {
r.text().then(res => {
this.setState({termsOfUseContent: res})
})
})
}
onUpdateAccount(account) {
this.props.onUpdateAccount(account);
}
@ -407,23 +419,25 @@ class SignupPage extends React.Component {
<Modal
title={i18next.t("signup:Terms of Use")}
visible={this.state.isTermsOfUseVisible}
width={"55vw"}
closable={false}
footer={[
<Button key="agree" type="primary" onClick={() => {
this.setState({
isTermsOfUseVisible: false,
});
}}>
{i18next.t("user:OK")}
</Button>,
// <Button key="decline" onClick={() => {
// this.props.history.goBack();
// }}>
// {i18next.t("signup:Decline")}
// </Button>,
]}
okText={i18next.t("signup:Accept")}
cancelText={i18next.t("signup:Decline")}
onOk={() => {
this.form.current.setFieldsValue({agreement: true})
this.setState({
isTermsOfUseVisible: false,
});
}}
onCancel={() => {
this.form.current.setFieldsValue({agreement: false})
this.setState({
isTermsOfUseVisible: false,
});
this.props.history.goBack();
}}
>
<div dangerouslySetInnerHTML={{__html: this.state.application?.termsOfUse}} />
<iframe style={{border: 0, width: "100%", height: "60vh"}} srcDoc={this.state.termsOfUseContent}/>
</Modal>
)
}

View File

@ -13,6 +13,7 @@
// limitations under the License.
import * as Setting from "../Setting";
import * as AuthBackend from "../auth/AuthBackend";
export function getApplications(owner) {
return fetch(`${Setting.ServerUrl}/api/get-applications?owner=${owner}`, {

View File

@ -79,18 +79,11 @@ export function getAffiliationOptions(url, code) {
export function uploadFile(folder, subFolder, file) {
let formData = new FormData();
formData.append("file", file);
fetch(`${Setting.ServerUrl}/api/upload-file?folder=${encodeURIComponent(folder)}&subFolder=${encodeURIComponent(subFolder)}`, {
return fetch(`${Setting.ServerUrl}/api/upload-file?folder=${encodeURIComponent(folder)}&subFolder=${encodeURIComponent(subFolder)}`, {
body: formData,
method: 'POST',
credentials: 'include',
}).then(res => res.json())
.then((res) => {
if (res.status === "ok") {
window.location.href = "/account";
} else {
Setting.showMessage("error", res.msg);
}
});
}
export function setPassword(userOwner, userName, oldPassword, newPassword) {

View File

@ -81,6 +81,9 @@
"Signin URL - Tooltip": "sign in url",
"ID - Tooltip": "random string",
"Favicon - Tooltip": "Application icon",
"Uploading": "Uploading",
"Start Upload": "Start Upload",
"Upload success": "Upload success",
"Back Home": "Back Home",
"Sorry, the page you visited does not exist": "Sorry, the page you visited does not exist"
},