diff --git a/client/model/share.js b/client/model/share.js index 469989f7..b6aca7c9 100644 --- a/client/model/share.js +++ b/client/model/share.js @@ -4,8 +4,13 @@ class ShareModel { constructor(){} all(path = "/"){ - const url = `api/share?path=${path}`; - return http_get(url); + const url = `/api/share?path=${path}`; + return http_get(url).then((res) => res.results); + } + + get(id){ + const url = `/api/share/${id}`; + return http_get(url).then((res) => res.result); } upsert(obj){ diff --git a/client/pages/filespage/share.js b/client/pages/filespage/share.js index 56e29722..35b5e105 100644 --- a/client/pages/filespage/share.js +++ b/client/pages/filespage/share.js @@ -13,42 +13,33 @@ export class ShareComponent extends React.Component { show_advanced: false, role: null, id: randomString(7), - existings: [ - {id: "dflkjse", role: "UPLOADER", path: "./test/test"}, - {id: "dflkjse", role: "VIEWER", path: "./test/test", password: "xxxx"}, - {id: "dflkjse", role: "EDITOR", path: "./test/test"}, - {id: "dflkjse", role: "VIEWER", path: "./test/test", password: "xxxx"}, - {id: "dflkjse", role: "EDITOR", path: "./test/test"}, - {id: "dflkjse", role: "UPLOADER", path: "./test/test"}, - ] + existings: [] }; } + componentDidMount(){ + Share.all(this.props.path) + .then((existings) => { + this.refreshModal(); + this.setState({existings: existings}); + }); + } + updateState(key, value){ if(this.state[key] === value){ this.setState({[key]: null}); }else{ this.setState({[key]: value}); } - - if(key === "role" && value && window.innerHeight < 500){ - window.dispatchEvent(new Event('resize')); + if(key === "role" && value){ + this.refreshModal(); } } - registerLink(e){ - e.target.setSelectionRange(0, e.target.value.length); - let st = Object.assign({}, this.state); - delete st.existings; - delete st.show_advanced; - this.setState({existing: [st].concat(this.state.existings)}); - return Share.upsert(st) - .catch((err) => { - notify.send(err, "error"); - this.setState({ - existings: this.state.existings.slice(0, this.state.existings.length) - }); - }); + refreshModal(){ + if(window.innerHeight < 500){ + window.dispatchEvent(new Event('resize')); + } } onLoad(link){ @@ -59,14 +50,44 @@ export class ShareComponent extends React.Component { this.setState(st); } - onDelete(link_id){ + onDeleteLink(link_id){ + let removed = null, + i = 0; + + for(i=0; i < this.state.existings.length; i++){ + if(this.state.existings[i].id === link_id){ + removed = Object.assign({}, this.state.existings[i]); + break; + } + } + if(removed !== null){ + this.state.existings.splice(i, 1); + this.setState({existings: this.state.existings}); + } + return Share.remove(link_id) - .then(() => { - console.log("HERE"); - }) - .catch((err) => notify.send(err, "error")); + .catch((err) => { + this.setState({existings: [removed].concat(this.state.existings)}); + notify.send(err, "error"); + }); } + onRegisterLink(e){ + e.target.setSelectionRange(0, e.target.value.length); + let st = Object.assign({}, this.state); + delete st.existings; + delete st.show_advanced; + this.setState({existings: [st].concat(this.state.existings)}); + return Share.upsert(st) + .catch((err) => { + notify.send(err, "error"); + this.setState({ + existings: this.state.existings.slice(0, this.state.existings.length) + }); + }); + } + + render(){ return (
@@ -90,10 +111,10 @@ export class ShareComponent extends React.Component { { this.state.existings && this.state.existings.map((link, i) => { return ( -
+
{link.role} {link.path} - +
); @@ -105,7 +126,7 @@ export class ShareComponent extends React.Component {

Restrictions

- +
@@ -124,7 +145,7 @@ export class ShareComponent extends React.Component {
- {}}/> + {}}/>
diff --git a/client/pages/filespage/share.scss b/client/pages/filespage/share.scss index d1f346db..033f4cb9 100644 --- a/client/pages/filespage/share.scss +++ b/client/pages/filespage/share.scss @@ -48,6 +48,7 @@ color: var(--light); display: inline-block; min-width: 75px; + text-transform: uppercase; } .component_icon{ width: 20px; diff --git a/client/pages/filespage/thing-existing.js b/client/pages/filespage/thing-existing.js index db6fca51..3568d079 100644 --- a/client/pages/filespage/thing-existing.js +++ b/client/pages/filespage/thing-existing.js @@ -185,7 +185,7 @@ export class ExistingThing extends React.Component { onShareRequest(filename){ alert.now( - , + , (ok) => {} ); } @@ -313,7 +313,7 @@ const ActionButton = (props) => { - + diff --git a/client/pages/filespage/thing.scss b/client/pages/filespage/thing.scss index 6d4869e4..32b91acc 100644 --- a/client/pages/filespage/thing.scss +++ b/client/pages/filespage/thing.scss @@ -111,6 +111,7 @@ margin: 2px; padding: 0; position: relative; + border: 3px solid transparent; > span > img{ padding: 0; margin: 0; diff --git a/client/pages/index.js b/client/pages/index.js index aa1e8783..2b78c18d 100644 --- a/client/pages/index.js +++ b/client/pages/index.js @@ -1,4 +1,5 @@ export { HomePage } from './homepage'; +export { SharePage } from './sharepage'; export { ConnectPage } from './connectpage'; export { LogoutPage } from './logout'; export { NotFoundPage } from './notfoundpage'; diff --git a/client/pages/sharepage.js b/client/pages/sharepage.js new file mode 100644 index 00000000..267b82f5 --- /dev/null +++ b/client/pages/sharepage.js @@ -0,0 +1,75 @@ +import React from 'react'; +import { Redirect } from 'react-router'; + +import { Share } from '../model/'; +import { notify } from '../helpers/'; +import { Loader, Input, Button, Container } from '../components/'; +import './error.scss'; + +export class SharePage extends React.Component { + constructor(props){ + super(props); + this.state = { + redirection: null, + loading: true, + request_password: false, + request_username: false + }; + } + + componentDidMount(){ + Share.get(this.props.match.params.id) + .then((res) => { + this.setState({ + loading: false, + redirection: res + }); + }) + .catch((res) => { + console.log(">> COMPONENT DID MOUNT:: ", res); + this.setState({ + loading: false + }); + }); + } + render() { + if(this.state.loading === true){ + return (
); + } + + if(this.state.request_password === true){ + return ( + +
+ + +
+
+ ); + }else if(this.state.request_username === true){ + return ( + +
+ + +
+
+ ); + } + + if(this.state.redirection !== null){ + if(this.state.redirection.slice(-1) === "/"){ + return ( ); + }else{ + return ( ); + } + }else{ + return ( +
+

Oops!

+

There's nothing in here

+
+ ); + } + } +} diff --git a/client/pages/viewerpage/imageviewer.scss b/client/pages/viewerpage/imageviewer.scss index 0c726574..92421727 100644 --- a/client/pages/viewerpage/imageviewer.scss +++ b/client/pages/viewerpage/imageviewer.scss @@ -135,6 +135,7 @@ margin: 5px 0; border-top: 1px dashed var(--color); padding-top: 5px; + clear: both; .title{ float: left; margin-right: 5px; } .value{ color: var(--bg-color); } } diff --git a/client/router.js b/client/router.js index 093123b3..cf95d642 100644 --- a/client/router.js +++ b/client/router.js @@ -1,6 +1,6 @@ import React from 'react'; import { BrowserRouter, Route, IndexRoute, Switch } from 'react-router-dom'; -import { NotFoundPage, ConnectPage, HomePage, LogoutPage, FilesPage, ViewerPage } from './pages/'; +import { NotFoundPage, ConnectPage, HomePage, SharePage, LogoutPage, FilesPage, ViewerPage } from './pages/'; import { Bundle, URL_HOME, URL_FILES, URL_VIEWER, URL_LOGIN, URL_LOGOUT } from './helpers/'; import { ModalPrompt, ModalAlert, ModalConfirm, Notification, Audio, Video } from './components/'; @@ -11,6 +11,7 @@ export default class AppRouter extends React.Component { + diff --git a/server/common/crypto.go b/server/common/crypto.go index 412897b9..d8eb8a32 100644 --- a/server/common/crypto.go +++ b/server/common/crypto.go @@ -4,6 +4,8 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" + "crypto/sha1" + "encoding/base32" "encoding/base64" "encoding/json" "io" @@ -49,3 +51,18 @@ func Decrypt(keystr string, cryptoText string) (map[string]string, error) { json.Unmarshal(ciphertext, &raw) return raw, nil } + +func GenerateID(params map[string]string) string { + p := "type =>" + params["type"] + p += "host =>" + params["host"] + p += "hostname =>" + params["hostname"] + p += "username =>" + params["username"] + p += "repo =>" + params["repo"] + p += "access_key_id =>" + params["access_key_id"] + p += "endpoint =>" + params["endpoint"] + p += "bearer =>" + params["bearer"] + p += "token =>" + params["token"] + hasher := sha1.New() + hasher.Write([]byte(p)) + return base32.HexEncoding.EncodeToString(hasher.Sum(nil)) +} diff --git a/server/common/utils.go b/server/common/utils.go index bdeab37b..cf9322d3 100644 --- a/server/common/utils.go +++ b/server/common/utils.go @@ -17,3 +17,7 @@ func RandomString(n int) string { func NewBool(t bool) *bool { return &t } + +func NewString(t string) *string { + return &t +} diff --git a/server/ctrl/share.go b/server/ctrl/share.go index 8c423b1f..96342003 100644 --- a/server/ctrl/share.go +++ b/server/ctrl/share.go @@ -8,27 +8,35 @@ import ( ) type ShareAPI struct { - Id string `json:"id"` - Path string `json:"path"` - Role string `json:"role"` - Password string `json:"password"` - Users []string `json:"users"` - CanManageOwn bool `json:"can_manage_own"` - CanShare bool `json:"can_share"` - Expire int `json:"expire"` - Link string `json:"link"` + Id string `json:"id"` + Path string `json:"path"` + Role string `json:"role"` + Password *string `json:"password"` + Users *[]string `json:"users"` + CanManageOwn *bool `json:"can_manage_own"` + CanShare *bool `json:"can_share"` + Expire *int `json:"expire"` + CustomURI *string `json:"uri"` } func ShareList(ctx App, res http.ResponseWriter, req *http.Request) { - p := extractParams(req) - listOfSharedLinks := model.ShareList(p) + s := extractParams(req, &ctx) + listOfSharedLinks := model.ShareList(s) SendSuccessResults(res, listOfSharedLinks) } +func ShareGet(ctx App, res http.ResponseWriter, req *http.Request) { + s := extractParams(req, &ctx) + if err := model.ShareGet(&s); err != nil { + SendErrorResult(res, err) + return + } + SendSuccessResult(res, s) +} + func ShareUpsert(ctx App, res http.ResponseWriter, req *http.Request) { - p := extractParams(req) - err := model.ShareUpsert(p, model.ShareParams{}) - if err != nil { + s := extractParams(req, &ctx) + if err := model.ShareUpsert(s); err != nil { SendErrorResult(res, err) return } @@ -36,16 +44,18 @@ func ShareUpsert(ctx App, res http.ResponseWriter, req *http.Request) { } func ShareDelete(ctx App, res http.ResponseWriter, req *http.Request) { - p := extractParams(req) - err := model.ShareDelete(p) - if err != nil { + s := extractParams(req, &ctx) + if err := model.ShareDelete(s); err != nil { SendErrorResult(res, err) return } SendSuccessResult(res, nil) } -func extractParams(req *http.Request) model.ShareKey { - vars := mux.Vars(req) - return model.ShareKey{vars["id"], "", ""} +func extractParams(req *http.Request, ctx *App) model.Share { + return model.Share{ + Id: NewString(mux.Vars(req)["id"]), + Backend: NewString(GenerateID(ctx.Session)), + Path: NewString(req.URL.Query().Get("path")), + } } diff --git a/server/model/backend/git.go b/server/model/backend/git.go index df4a7ddc..2b3061a5 100644 --- a/server/model/backend/git.go +++ b/server/model/backend/git.go @@ -3,7 +3,6 @@ package backend import ( "fmt" . "github.com/mickael-kerjean/nuage/server/common" - "github.com/mitchellh/hashstructure" "golang.org/x/crypto/ssh" "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing" @@ -95,10 +94,7 @@ func NewGit(params map[string]string, app *App) (*Git, error) { return nil, NewError("Your password doesn't fit in a cookie :/", 500) } - hash, err := hashstructure.Hash(params, nil) - if err != nil { - return nil, NewError("Internal error", 500) - } + hash := GenerateID(params) p.basePath = app.Helpers.AbsolutePath(GitCachePath + "repo_" + fmt.Sprint(hash) + "/") repo, err := g.git.open(p, p.basePath) diff --git a/server/model/permissions.go b/server/model/permissions.go new file mode 100644 index 00000000..c5967088 --- /dev/null +++ b/server/model/permissions.go @@ -0,0 +1,9 @@ +package model + +func CanRemoveShare() bool { + return false +} + +func CanEditShare() bool { + return false +} diff --git a/server/model/share.go b/server/model/share.go new file mode 100644 index 00000000..25057cfb --- /dev/null +++ b/server/model/share.go @@ -0,0 +1,135 @@ +package model + +import ( + "database/sql" + _ "github.com/mattn/go-sqlite3" + . "github.com/mickael-kerjean/nuage/server/common" + "log" + "os" + "path/filepath" +) + +const DBCachePath = "data/" + +type Share struct { + Id *string `json:"id"` + Backend *string `json:"-"` + Path *string `json:"path"` + Params struct { + } `json:"-"` + Role *string `json:"role"` + Password *string `json:"password,omitempty"` + Users *[]string `json:"-"` + Expire *int `json:"expire,omitempty"` + CanRead *bool `json:"-"` + CanWrite *bool `json:"-"` + CanUpload *bool `json:"-"` + CanShare *bool `json:"can_share,omitempty"` + CanManageOwn *bool `json:"can_manage_own,omitempty"` +} + +func init() { + cachePath := filepath.Join(GetCurrentDir(), DBCachePath) + os.MkdirAll(cachePath, os.ModePerm) + + db, err := sql.Open("sqlite3", cachePath+"/db.sql") + if err != nil { + return + } + stmt, err := db.Prepare("CREATE TABLE IF NOT EXISTS Location(backend VARCHAR(16), path VARCHAR(512), CONSTRAINT pk_location PRIMARY KEY(backend, path))") + if err != nil { + return + } + stmt.Exec() + + stmt, err = db.Prepare("CREATE TABLE IF NOT EXISTS Share(id VARCHAR(64) PRIMARY KEY, related_backend VARCHAR(16), related_path VARCHAR(512), params JSON, FOREIGN KEY (related_backend, related_path) REFERENCES Location(backend, path) ON UPDATE CASCADE ON DELETE CASCADE)") + if err != nil { + return + } + stmt.Exec() +} + +func ShareList(p Share) []Share { + db, err := getDb() + if err != nil { + return nil + } + log.Println("- backend: ", p.Backend) + stmt, err := db.Prepare("SELECT s.id, l.path, s.params FROM Share as s LEFT JOIN Location as l ON l.backend = s.related_backend") + log.Println("err1:", err) + if err != nil { + return nil + } + rows, err := stmt.Query() + log.Println(">> ROWS::", rows) + log.Println("err2:", err) + if err != nil { + return nil + } + defer rows.Close() + + sharedFiles := []Share{} + for rows.Next() { + var a Share + //var params string + rows.Scan(&a.Id, &a.Path, &a.Role) + a.Role = NewString("viewer") + sharedFiles = append(sharedFiles, a) + } + return sharedFiles +} + +func ShareGet(p *Share) error { + db, err := getDb() + if err != nil { + return err + } + stmt, err := db.Prepare("SELECT id, related_path, params FROM share WHERE id = ?") + if err != nil { + return err + } + defer stmt.Close() + row := stmt.QueryRow(p.Id) + row.Scan(&p.Id, &p.Path) + return nil +} + +func ShareUpsert(p Share) error { + db, err := getDb() + if err != nil { + return err + } + + stmt, err := db.Prepare("INSERT INTO Share(id, related_backend, related_path, params) VALUES($1, $2, $3, $4) ON CONFLICT(id) DO UPDATE SET related_backend = $,2s related_path = $3, params = $4") + if err != nil { + return err + } + _, err = stmt.Exec(p.Id, p.Backend, p.Path, "{}") + return err +} + +func ShareDelete(p Share) error { + db, err := getDb() + if err != nil { + return err + } + stmt, err := db.Prepare("DELETE FROM Share WHERE id = ?") + if err != nil { + return err + } + _, err = stmt.Exec(p.Id) + return err +} + +func getDb() (*sql.DB, error) { + path := filepath.Join(GetCurrentDir(), DBCachePath) + "/db.sql" + return sql.Open("sqlite3", path) +} + +func shareToDBParams(s Share) string { + return "" +} + +func DPParamstoShare() Share { + return Share{} +} diff --git a/server/router/index.go b/server/router/index.go index 2995334e..631071d4 100644 --- a/server/router/index.go +++ b/server/router/index.go @@ -29,6 +29,7 @@ func Init(a *App) *http.Server { share := r.PathPrefix("/api/share").Subrouter() share.HandleFunc("", APIHandler(ShareList, *a)).Methods("GET") + share.HandleFunc("/{id}", APIHandler(ShareGet, *a)).Methods("GET") share.HandleFunc("/{id}", APIHandler(ShareUpsert, *a)).Methods("POST") share.HandleFunc("/{id}", APIHandler(ShareDelete, *a)).Methods("DELETE")