#141: Started file fixing. Added file download handler

This commit is contained in:
frikky
2020-10-29 10:57:05 +01:00
parent dd4af346d3
commit fdba86f9c3
7 changed files with 272 additions and 52 deletions
+2 -2
View File
@@ -400,7 +400,7 @@ class AppBase:
# Means it's a single item -> continue
if seconditem == "":
print("In first - handling %s", seconditem)
print("In first - handling %s" % seconditem)
tmpitem = basejson[int(firstitem)]
try:
newvalue, is_loop = recurse_json(tmpitem, parsersplit[outercnt+1:])
@@ -884,7 +884,7 @@ class AppBase:
# Custom format for ${name[0,1,2,...]}$
#submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})"
submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})"
submatch = "([${]{2}#?([0-9a-zA-Z_-]+)#?(\[.*\])[}$]{2})"
actualitem = re.findall(submatch, value, re.MULTILINE)
try:
if action["skip_multicheck"]:
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash
NAME=app_sdk
VERSION=0.7.5
VERSION=0.7.6
docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force
docker build . -t frikky/shuffle:$NAME -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
+210 -11
View File
@@ -1790,16 +1790,16 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
currentOrg = []byte("{}")
}
returnData := fmt.Sprintf(`
{
"success": true,
"admin": %s,
"tutorials": [],
"id": "%s",
"orgs": [%s],
"active_org": %s,
"cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]
}`, parsedAdmin, userInfo.Id, currentOrg, currentOrg, userInfo.Session, expiration.Unix())
returnData := fmt.Sprintf(`{
"success": true,
"username": "%s",
"admin": %s,
"tutorials": [],
"id": "%s",
"orgs": [%s],
"active_org": %s,
"cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]
}`, userInfo.Username, parsedAdmin, userInfo.Id, currentOrg, currentOrg, userInfo.Session, expiration.Unix())
resp.WriteHeader(200)
resp.Write([]byte(returnData))
@@ -2537,6 +2537,28 @@ func getSession(ctx context.Context, thissession string) (*session, error) {
return curUser, nil
}
// ListBooks returns a list of books, ordered by title.
func getFile(ctx context.Context, id string) (*File, error) {
key := datastore.NameKey("Files", id, nil)
curFile := &File{}
if err := dbclient.Get(ctx, key, curFile); err != nil {
return &File{}, err
}
return curFile, nil
}
func setFile(ctx context.Context, file File) error {
// clear session_token and API_token for user
k := datastore.NameKey("Files", file.Id, nil)
if _, err := dbclient.Put(ctx, k, &file); err != nil {
log.Println(err)
return err
}
return nil
}
// ListBooks returns a list of books, ordered by title.
func getOrg(ctx context.Context, id string) (*Org, error) {
key := datastore.NameKey("Organizations", id, nil)
@@ -6648,6 +6670,38 @@ func runInit(ctx context.Context) {
log.Printf("Set workflow orgs for %d workflows", updated)
}
}
fileq := datastore.NewQuery("Files").Limit(1)
count, err := dbclient.Count(ctx, fileq)
if err == nil && count == 0 {
basepath := "."
filename := "testfile.txt"
fileId := uuid.NewV4().String()
log.Printf("Creating new file reference %s because none exist!", fileId)
workflowId := "2e9d6474-402c-4dcc-bb53-45f638ca18d3"
downloadPath := fmt.Sprintf("%s/%s/%s/%s", basepath, activeOrgs[0].Id, workflowId, fileId)
timeNow := time.Now().Unix()
newFile := File{
Id: fileId,
CreatedAt: timeNow,
UpdatedAt: timeNow,
Description: "Created by system for testing",
Status: "active",
Filename: filename,
OrgId: activeOrgs[0].Id,
WorkflowId: workflowId,
DownloadPath: downloadPath,
}
err = setFile(ctx, newFile)
if err != nil {
log.Printf("Failed setting file: %s", err)
} else {
log.Printf("Created file %s in init", newFile.DownloadPath)
}
}
}
// Gets schedules and starts them
@@ -7183,6 +7237,141 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
resp.Write(respBody)
}
type File struct {
Id string `json:"id" datastore:"id"`
Type string `json:"type" datastore:"type"`
CreatedAt int64 `json:"created_at" datastore:"created_at"`
UpdatedAt int64 `json:"updated_at" datastore:"updated_at"`
CreatedBy struct {
ID int `json:"id" datastore:"id"`
Type string `json:"type" datastore:"type"`
Login string `json:"login" datastore:"login"`
Name string `json:"name" datastore:"name"`
} `json:"created_by" datastore:"created_by"`
Description string `json:"description" datastore:"description"`
Etag int `json:"etag" datastore:"etag"`
ExpiresAt string `json:"expires_at" datastore:"expires_at"`
Folder struct {
ID int `json:"id" datastore:"id"`
Type string `json:"type" datastore:"type"`
Etag int `json:"etag" datastore:"etag"`
Name string `json:"name" datastore:"name"`
SequenceID int `json:"sequence_id" datastore:"sequence_id"`
} `json:"folder" datastore:"folder"`
Status string `json:"status" datastore:"status"`
Filename string `json:"filename" datastore:"filename"`
UpdatedBy struct {
ID int `json:"id" datastore:"id"`
Type string `json:"type" datastore:"type"`
Login string `json:"login" datastore:"login"`
Name string `json:"name" datastore:"name"`
} `json:"updated_by" datastore:"updated_by"`
URL string `json:"url" datastore:"org"`
OrgId string `json:"org_id" datastore:"org_id"`
WorkflowId string `json:"workflow_id" datastore:"workflow_id"`
Workflows []string `json:"workflows" datastore:"workflows"`
DownloadPath string `json:"download_path" datastore:"download_path"`
}
func handleGetFileContent(resp http.ResponseWriter, request *http.Request) {
cors := handleCors(resp, request)
if cors {
return
}
var fileId string
location := strings.Split(request.URL.String(), "/")
if location[1] == "api" {
if len(location) <= 4 {
log.Printf("Path too short: %d", len(location))
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
fileId = location[4]
}
//log.Printf("In file download")
user, err := handleApiAuthentication(resp, request)
if err != nil {
log.Printf("Api authentication failed in file download: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
// 1. Verify if the user has access to the file: org_id and workflow
log.Printf("Should get file %s", fileId)
ctx := context.Background()
file, err := getFile(ctx, fileId)
if err != nil {
log.Printf("File %s not found: %s", fileId, err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
found := false
if file.OrgId == user.ActiveOrg.Id {
found = true
} else {
for _, item := range user.Orgs {
if item == file.OrgId {
found = true
break
}
}
}
if !found {
log.Printf("User %s doesn't have access to %s", user.Username, fileId)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
// Fixme: More auth: org and workflow!
downloadPath := file.DownloadPath
log.Printf("Downloadpath: %s", downloadPath)
Openfile, err := os.Open(downloadPath)
defer Openfile.Close() //Close after function return
if err != nil {
//File not found, send 404
http.Error(resp, "File not found.", 404)
return
}
//File is found, create and send the correct headers
//Get the Content-Type of the file
//Create a buffer to store the header of the file in
FileHeader := make([]byte, 512)
//Copy the headers into the FileHeader buffer
Openfile.Read(FileHeader)
//Get content type of file
FileContentType := http.DetectContentType(FileHeader)
//Get the file size
FileStat, _ := Openfile.Stat() //Get info from file
FileSize := strconv.FormatInt(FileStat.Size(), 10) //Get file size as a string
//Send the headers
resp.Header().Set("Content-Disposition", "attachment; filename="+fileId)
resp.Header().Set("Content-Type", FileContentType)
resp.Header().Set("Content-Length", FileSize)
//Send the file
//We read 512 bytes from the file already, so we reset the offset back to 0
Openfile.Seek(0, 0)
io.Copy(resp, Openfile) //'Copy' the file to the client
return
//log.Printf("Should download file %s", downloadPath)
//resp.WriteHeader(200)
//resp.Write([]byte("OK"))
}
func initHandlers() {
var err error
ctx := context.Background()
@@ -7302,7 +7491,17 @@ func initHandlers() {
// NEW for 0.8.0
r.HandleFunc("/api/v1/cloud/setup", handleCloudSetup).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/getorgs", handleGetOrgs).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs", handleGetOrgs).Methods("GET", "OPTIONS")
// Important for email, IDS etc. Create this by:
// PS: For cloud, this has to use cloud storage.
// https://developer.box.com/reference/get-files-id-content/
// 1. Creating the "get file" option. Make it possible to run this in the frontend.
r.HandleFunc("/api/v1/files/{fileId}/content", handleGetFileContent).Methods("GET", "OPTIONS")
//r.HandleFunc("/api/v1/files/create", handleGetFile).Methods("POST", "OPTIONS")
//r.HandleFunc("/api/v1/files/upload", handleGetFile).Methods("POST", "OPTIONS")
//r.HandleFunc("/api/v1/files/{fileId}", handleGetFile).Methods("GET", "OPTIONS")
//r.HandleFunc("/api/v1/files/{fileId}", handleGetFile).Methods("DELETE", "OPTIONS")
http.Handle("/", r)
}
+3
View File
@@ -0,0 +1,3 @@
# ./b199646b-16d2-456d-9fd6-b9972e929466/2e9d6474-402c-4dcc-bb53-45f638ca18d3/0d676d72-5d53-4803-a6b0-4afb464df828
# org_id / workflow_id / file_id
curl http://192.168.3.6:5001/api/v1/files/0d676d72-5d53-4803-a6b0-4afb464df828/content -H "Authorization: Bearer 093b576f-19ea-4353-b685-362ab50f39f4"
+29 -11
View File
@@ -35,7 +35,7 @@ const Header = props => {
// DEBUG HERE
const handleClickLogout = () => {
console.log("SHOULD LOG OUT")
console.log("SHOULD LOG OUT")
console.log(isLoggedIn)
// Don't really care about the logout
@@ -47,10 +47,9 @@ const Header = props => {
},
})
.then(() => {
// Log out anyway
console.log("Hey")
removeCookie("session_token", {path: "/"})
window.location.pathname = "/"
// Log out anyway
removeCookie("session_token", {path: "/"})
//window.location.pathname = "/"
})
.catch(error => {
console.log(error)
@@ -158,6 +157,16 @@ const Header = props => {
</div>
</Link>
</ListItem>
{/*
<ListItem style={{textAlign: "center"}}>
<Link to="/pricing" style={hrefStyle}>
<div onMouseOver={handleDocsHover} onMouseOut={handleDocsHoverOut} style={{color: DocsHoverColor, cursor: "pointer", display: "flex"}}>
<DescriptionIcon style={{marginRight: "5px"}} />
<span style={{marginTop: 2}}>Pricing</span>
</div>
</Link>
</ListItem>
*/}
{/*
<ListItem style={{textAlign: "center"}}>
<Link to="/configurations" style={hrefStyle}>
@@ -183,6 +192,19 @@ const Header = props => {
color="primary"> Settings</Button>
</Link>
</ListItem>
{/*
<ListItem>
<Link to="/contact" style={hrefStyle}>
<Button
style={{}}
variant="contained"
color="primary"
>
Contact
</Button>
</Link>
</ListItem>
*/}
{userdata === undefined || userdata.admin === undefined || userdata.admin === null || !userdata.admin ? null :
<ListItem>
<Link to="/admin" style={hrefStyle}>
@@ -299,8 +321,8 @@ const Header = props => {
</div>
// <Divider style={{height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
const loadedCheck = isLoaded ?
<div>
const loadedCheck =
<div style={{minHeight: 68}}>
<BrowserView>
{loginTextBrowser}
</BrowserView>
@@ -308,10 +330,6 @@ const Header = props => {
{loginTextMobile}
</MobileView>
</div>
:
<div>
</div>
// <div style={{backgroundImage: "linear-gradient(-90deg,#342f78 0,#29255e 50%,#1b1947 100%"}}>
return (
<div>
+1 -1
View File
@@ -555,7 +555,7 @@ const Admin = (props) => {
}
const getOrgs = () => {
fetch(globalUrl + "/api/v1/getorgs", {
fetch(globalUrl + "/api/v1/orgs", {
method: 'GET',
headers: {
'Content-Type': 'application/json',
+26 -26
View File
@@ -109,13 +109,13 @@ const Settings = (props) => {
const getSettings = () => {
fetch(globalUrl+"/api/v1/getsettings", {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
})
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!")
@@ -137,24 +137,24 @@ const Settings = (props) => {
if (userInfo.username.length > 0) {
setUsername(userInfo.username)
}
if (userInfo.firstname.length > 0) {
setFirstname(userInfo.firstname)
}
if (userInfo.lastname.length > 0) {
setLastname(userInfo.lastname)
}
if (userInfo.title.length > 0) {
setTitle(userInfo.title)
}
if (userInfo.companyname.length > 0) {
setCompanyname(userInfo.companyname)
}
if (userInfo.phone.length > 0) {
setPhone(userInfo.phone)
}
if (userInfo.email.length > 0) {
setEmail(userInfo.email)
}
//if (userInfo.firstname.length > 0) {
// setFirstname(userInfo.firstname)
//}
//if (userInfo.lastname.length > 0) {
// setLastname(userInfo.lastname)
//}
//if (userInfo.title.length > 0) {
// setTitle(userInfo.title)
//}
//if (userInfo.companyname.length > 0) {
// setCompanyname(userInfo.companyname)
//}
//if (userInfo.phone.length > 0) {
// setPhone(userInfo.phone)
//}
//if (userInfo.email.length > 0) {
// setEmail(userInfo.email)
//}
}
}
@@ -175,7 +175,7 @@ const Settings = (props) => {
<div style={{display: "flex", marginTop: "80px"}}>
<Paper style={boxStyle}>
<h2>APIKEY</h2>
<Link to="/docs/API#authentication" style={{textDecoration: "none", color: "#f85a3e"}}>What is the API key used for?</Link>
<a target="_blank" href="/docs/API#authentication" style={{textDecoration: "none", color: "#f85a3e"}}>What is the API key used for?</a>
<TextField
style={{backgroundColor: theme.palette.inputColor, flex: "1"}}
InputProps={{