@ -0,0 +1,2 @@ |
|||||
|
ipfsStorage |
||||
|
reposStorage |
@ -0,0 +1,81 @@ |
|||||
|
# padArchiver |
||||
|
Tool to store a pad (from the link) into IPFS and Git. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## padArchiver-APIserver |
||||
|
This is an API to run in localhost. |
||||
|
|
||||
|
#### Run |
||||
|
To run using the compiled binary: |
||||
|
- The Git repo needs to be initialized, and with the remote already configured. |
||||
|
- The IPFS daemon needs to be running: |
||||
|
``` |
||||
|
> ipfs daemon |
||||
|
``` |
||||
|
|
||||
|
- Edit the file config.json to configure the desired port: |
||||
|
``` |
||||
|
{ |
||||
|
"port": "3080" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
- Execute the API server: |
||||
|
``` |
||||
|
> ./padArchiver-APIserver |
||||
|
``` |
||||
|
|
||||
|
#### API Routes |
||||
|
|
||||
|
###### - GET /repos |
||||
|
this returns: |
||||
|
``` |
||||
|
[ |
||||
|
'repo01', |
||||
|
'repo02' |
||||
|
] |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
###### - GET /repos/{repoid} |
||||
|
this returns: |
||||
|
``` |
||||
|
[ |
||||
|
'repo01', |
||||
|
'repo01/Group1', |
||||
|
'repo01/Group1/Pad1.md', |
||||
|
'repo01/Group2', |
||||
|
'repo01/Group2/Pad2.md', |
||||
|
'repo01/Group2/Pad3.md', |
||||
|
'repo02/GroupA/Pad1.md' |
||||
|
] |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
###### - POST /repos/{repoid}/pad |
||||
|
data to send: |
||||
|
``` |
||||
|
json: { |
||||
|
"link": "http://board.net/p/pad1", |
||||
|
"dir": "Group1", |
||||
|
"title": "Pad1" |
||||
|
} |
||||
|
``` |
||||
|
this returns: |
||||
|
``` |
||||
|
{ |
||||
|
"link": "http://board.net/p/pad1", |
||||
|
"dir": "Group1", |
||||
|
"title": "Pad1", |
||||
|
"ipfsHash": "QmVyp4JSREK5syLmNRCafkZkhzC7CfvS9qYWKfvfffqK2B" |
||||
|
} |
||||
|
``` |
||||
|
The IPFS hash is also added to the first line of the document, before adding the document to Git. |
||||
|
|
||||
|
## padArchiver-cli |
||||
|
To run the CLI, just need to run: |
||||
|
``` |
||||
|
./padArchiver-cli |
||||
|
``` |
||||
|
And follow the instructions. |
@ -0,0 +1,9 @@ |
|||||
|
package padArchiver |
||||
|
|
||||
|
import "github.com/fatih/color" |
||||
|
|
||||
|
func check(err error) { |
||||
|
if err != nil { |
||||
|
color.Red(err.Error()) |
||||
|
} |
||||
|
} |
@ -0,0 +1,46 @@ |
|||||
|
package padArchiver |
||||
|
|
||||
|
import ( |
||||
|
"errors" |
||||
|
"fmt" |
||||
|
"io/ioutil" |
||||
|
"net/http" |
||||
|
"os" |
||||
|
) |
||||
|
|
||||
|
func (repo *Repo) GetPad(link string, extension string, directory string, title string) (string, error) { |
||||
|
if extension != "md" && extension != "txt" && extension != "html" && extension != "pdf" && extension != "odt" { |
||||
|
return "", errors.New("No valid extension") |
||||
|
} |
||||
|
format := extension |
||||
|
if extension == "md" { |
||||
|
format = "markdown" |
||||
|
extension = "md" |
||||
|
} |
||||
|
|
||||
|
//create the pads directory
|
||||
|
_ = os.Mkdir(repo.Dir+"/"+directory, os.ModePerm) |
||||
|
|
||||
|
completeLink := link + "/export/" + format |
||||
|
|
||||
|
//get the content from the url
|
||||
|
r, err := http.Get(completeLink) |
||||
|
if err != nil { |
||||
|
fmt.Println(err) |
||||
|
return "", err |
||||
|
} |
||||
|
defer r.Body.Close() |
||||
|
content, err := ioutil.ReadAll(r.Body) |
||||
|
if err != nil { |
||||
|
fmt.Printf("%s", err) |
||||
|
return "", err |
||||
|
} |
||||
|
|
||||
|
//save the content into a file
|
||||
|
err = ioutil.WriteFile(repo.Dir+"/"+directory+"/"+title+"."+extension, content, 0644) |
||||
|
if err != nil { |
||||
|
fmt.Println(err) |
||||
|
return "", err |
||||
|
} |
||||
|
return repo.Dir + "/" + directory + "/" + title + "." + extension, nil |
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
package padArchiver |
||||
|
|
||||
|
import ( |
||||
|
"io/ioutil" |
||||
|
) |
||||
|
|
||||
|
func AddLineToFile(path string, line string) error { |
||||
|
fileBytes, err := ioutil.ReadFile(path) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
content := string(fileBytes) |
||||
|
r := line + "\n\n" |
||||
|
r = r + content |
||||
|
|
||||
|
err = ioutil.WriteFile(path, []byte(r), 0644) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
return nil |
||||
|
} |
@ -0,0 +1,26 @@ |
|||||
|
package padArchiver |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"os/exec" |
||||
|
) |
||||
|
|
||||
|
//TODO this is not finished
|
||||
|
func (repo *Repo) GitUpdate(commitMsg string) error { |
||||
|
_, err := exec.Command("bash", "-c", "git pull origin master").Output() |
||||
|
if err != nil { |
||||
|
fmt.Println(err) |
||||
|
return err |
||||
|
} |
||||
|
_, err = exec.Command("bash", "-c", "git add .").Output() |
||||
|
if err != nil { |
||||
|
fmt.Println(err) |
||||
|
return err |
||||
|
} |
||||
|
_, err = exec.Command("bash", "-c", "git commit -m '"+commitMsg+"'").Output() |
||||
|
if err != nil { |
||||
|
fmt.Println(err) |
||||
|
return err |
||||
|
} |
||||
|
return nil |
||||
|
} |
@ -0,0 +1,41 @@ |
|||||
|
package padArchiver |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"os" |
||||
|
|
||||
|
sh "github.com/ipfs/go-ipfs-api" |
||||
|
) |
||||
|
|
||||
|
//GettedPads is the directory where are stored the pads that are getted from IPFS
|
||||
|
const IpfsStorage = "ipfsStorage" |
||||
|
const GettedPads = "ipfsStorage/gettedPads" |
||||
|
|
||||
|
//Add gets the content from the etherpad specified in the link, and downloads it in the format of the specified extension, and then, puts it into IPFS
|
||||
|
func IpfsAdd(path string) (string, error) { |
||||
|
//connect to ipfs shell
|
||||
|
s := sh.NewShell("localhost:5001") |
||||
|
//save the file into IPFS
|
||||
|
ipfsHash, err := s.AddDir(path) |
||||
|
if err != nil { |
||||
|
fmt.Println(err) |
||||
|
return "", err |
||||
|
} |
||||
|
return ipfsHash, nil |
||||
|
} |
||||
|
|
||||
|
//Get gets the content from IPFS for a given hash, and saves it into a file
|
||||
|
func IpfsGet(hash string, filename string) error { //create the pads directory
|
||||
|
//create the pads directory
|
||||
|
_ = os.Mkdir(IpfsStorage, os.ModePerm) |
||||
|
_ = os.Mkdir(GettedPads, os.ModePerm) |
||||
|
|
||||
|
//connect to ipfs shell
|
||||
|
s := sh.NewShell("localhost:5001") |
||||
|
err := s.Get(hash, GettedPads+"/"+filename) |
||||
|
if err != nil { |
||||
|
fmt.Println(err) |
||||
|
return err |
||||
|
} |
||||
|
return nil |
||||
|
} |
@ -0,0 +1,3 @@ |
|||||
|
{ |
||||
|
"port": "3080" |
||||
|
} |
@ -0,0 +1,9 @@ |
|||||
|
package main |
||||
|
|
||||
|
import "log" |
||||
|
|
||||
|
func check(err error) { |
||||
|
if err != nil { |
||||
|
log.Println(err) |
||||
|
} |
||||
|
} |
@ -0,0 +1,100 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
"log" |
||||
|
"net/http" |
||||
|
"os" |
||||
|
"path/filepath" |
||||
|
"strings" |
||||
|
|
||||
|
padArchiver ".." |
||||
|
"github.com/gorilla/mux" |
||||
|
) |
||||
|
|
||||
|
type PadModel struct { |
||||
|
Link string `json:"link"` |
||||
|
Dir string `json:"dir"` |
||||
|
Title string `json:"title"` |
||||
|
IpfsHash string `json:"ipfsHash"` |
||||
|
} |
||||
|
type Repo struct { |
||||
|
Pads []string `json:"pads"` |
||||
|
} |
||||
|
|
||||
|
func main() { |
||||
|
readConfig("config.json") |
||||
|
|
||||
|
router := mux.NewRouter() |
||||
|
router.HandleFunc("/repos", GetReposList).Methods("GET") |
||||
|
router.HandleFunc("/repos/{repoid}", GetRepoIDList).Methods("GET") |
||||
|
router.HandleFunc("/repos/{repoid}/pad", PostStorePad).Methods("POST") |
||||
|
|
||||
|
log.Println("padArchiver API server running") |
||||
|
log.Print("port: ") |
||||
|
log.Println(config.Port) |
||||
|
log.Fatal(http.ListenAndServe(":"+config.Port, router)) |
||||
|
} |
||||
|
|
||||
|
func GetReposList(w http.ResponseWriter, r *http.Request) { |
||||
|
file, err := os.Open(padArchiver.Storage) |
||||
|
if err != nil { |
||||
|
log.Fatalf("failed opening directory: %s", err) |
||||
|
} |
||||
|
defer file.Close() |
||||
|
|
||||
|
list, _ := file.Readdirnames(0) // 0 to read all files and folders
|
||||
|
|
||||
|
var repos []string |
||||
|
for _, name := range list { |
||||
|
repos = append(repos, strings.Replace(name, padArchiver.Storage+"/", "", -1)) |
||||
|
} |
||||
|
|
||||
|
jResp, err := json.Marshal(repos) |
||||
|
check(err) |
||||
|
fmt.Fprintln(w, string(jResp)) |
||||
|
} |
||||
|
|
||||
|
func GetRepoIDList(w http.ResponseWriter, r *http.Request) { |
||||
|
vars := mux.Vars(r) |
||||
|
repoid := vars["repoid"] |
||||
|
|
||||
|
fileList := []string{} |
||||
|
err := filepath.Walk(padArchiver.Storage+"/"+repoid, func(path string, f os.FileInfo, err error) error { |
||||
|
fileList = append(fileList, path) |
||||
|
return nil |
||||
|
}) |
||||
|
|
||||
|
var files []string |
||||
|
for _, file := range fileList { |
||||
|
files = append(files, strings.Replace(file, padArchiver.Storage+"/", "", -1)) |
||||
|
} |
||||
|
jResp, err := json.Marshal(files) |
||||
|
check(err) |
||||
|
fmt.Fprintln(w, string(jResp)) |
||||
|
} |
||||
|
func PostStorePad(w http.ResponseWriter, r *http.Request) { |
||||
|
vars := mux.Vars(r) |
||||
|
repoid := vars["repoid"] |
||||
|
|
||||
|
//open the repo
|
||||
|
repo := padArchiver.OpenRepo(repoid) |
||||
|
//get the pad json
|
||||
|
var pad PadModel |
||||
|
decoder := json.NewDecoder(r.Body) |
||||
|
err := decoder.Decode(&pad) |
||||
|
if err != nil { |
||||
|
panic(err) |
||||
|
} |
||||
|
defer r.Body.Close() |
||||
|
|
||||
|
ipfsHash, err := repo.StorePad(pad.Link, pad.Dir, pad.Title) |
||||
|
if err != nil { |
||||
|
http.Error(w, "error storing pad", http.StatusConflict) |
||||
|
} |
||||
|
pad.IpfsHash = ipfsHash |
||||
|
jResp, err := json.Marshal(pad) |
||||
|
check(err) |
||||
|
fmt.Fprintln(w, string(jResp)) |
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"io/ioutil" |
||||
|
) |
||||
|
|
||||
|
type Config struct { |
||||
|
Port string `json:"port"` |
||||
|
} |
||||
|
|
||||
|
var config Config |
||||
|
|
||||
|
func readConfig(path string) { |
||||
|
file, err := ioutil.ReadFile(path) |
||||
|
check(err) |
||||
|
content := string(file) |
||||
|
json.Unmarshal([]byte(content), &config) |
||||
|
} |
@ -0,0 +1,48 @@ |
|||||
|
''' |
||||
|
this script uses requests and provoj |
||||
|
provoj is a simple library to test endpoints of an API RESTful |
||||
|
provoj can be downloaded using: pip install provoj |
||||
|
provoj repository: https://github.com/arnaucode/provoj |
||||
|
|
||||
|
To run this test, just need to run: |
||||
|
python test.py |
||||
|
''' |
||||
|
import provoj |
||||
|
import requests |
||||
|
|
||||
|
|
||||
|
test = provoj.NewTest("testing padArchiver API Server") |
||||
|
|
||||
|
url = "http://127.0.0.1:3080" |
||||
|
|
||||
|
|
||||
|
|
||||
|
jsonData = {"link": "http://board.net/p/pad1", "dir": "Group1", "title": "Pad1"} |
||||
|
r = requests.post(url + "/repos/repo01/pad", json=jsonData) |
||||
|
test.rStatus("POST add new pad", r) |
||||
|
print(r.json()) |
||||
|
|
||||
|
jsonData = {"link": "http://board.net/p/pad2", "dir": "Group2", "title": "Pad2"} |
||||
|
r = requests.post(url + "/repos/repo01/pad", json=jsonData) |
||||
|
test.rStatus("POST add new pad", r) |
||||
|
print(r.json()) |
||||
|
|
||||
|
jsonData = {"link": "http://board.net/p/pad3", "dir": "Group2", "title": "Pad3"} |
||||
|
r = requests.post(url + "/repos/repo01/pad", json=jsonData) |
||||
|
test.rStatus("POST add new pad", r) |
||||
|
print(r.json()) |
||||
|
|
||||
|
|
||||
|
r = requests.get(url + "/repos") |
||||
|
test.rStatus("GET repos list", r) |
||||
|
print(r.json()) |
||||
|
reposList = r.json() |
||||
|
testRepo = reposList[0] |
||||
|
|
||||
|
r = requests.get(url + "/repos/" + testRepo) |
||||
|
test.rStatus("GET repo " + testRepo + " list", r) |
||||
|
print(r.json()) |
||||
|
|
||||
|
|
||||
|
|
||||
|
test.printScores() |
@ -0,0 +1,112 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"bufio" |
||||
|
"fmt" |
||||
|
"os" |
||||
|
"strings" |
||||
|
|
||||
|
padArchiver ".." |
||||
|
"github.com/fatih/color" |
||||
|
) |
||||
|
|
||||
|
const checkIcon = "\xE2\x9C\x94 " |
||||
|
|
||||
|
func main() { |
||||
|
asciiart := ` |
||||
|
. |
||||
|
. _ _ _ |
||||
|
. | | /\ | | (_) |
||||
|
_ __ __ _ __| | / \ _ __ ___| |__ ___ _____ _ __ |
||||
|
| '_ \ / _ |/ _ | / /\ \ | '__/ __| '_ \| \ \ / / _ \ '__| |
||||
|
| |_) | (_| | (_| |/ ____ \| | | (__| | | | |\ V / __/ | |
||||
|
| .__/ \__,_|\__,_/_/ \_\_| \___|_| |_|_| \_/ \___|_| - cli |
||||
|
| | |
||||
|
|_| |
||||
|
|
||||
|
` |
||||
|
color.Blue(asciiart) |
||||
|
fmt.Println(" v0.0.1") |
||||
|
color.Blue("https://github.com/arnaucode/padArchiver") |
||||
|
fmt.Println("") |
||||
|
fmt.Println("") |
||||
|
fmt.Println("") |
||||
|
|
||||
|
newcommand := bufio.NewReader(os.Stdin) |
||||
|
fmt.Print("Please select command number") |
||||
|
options := ` |
||||
|
1 - Store Pad (to IPFS, Git, and send Telegram notification) |
||||
|
2 - IPFS hash to file |
||||
|
0 - Exit cli |
||||
|
option to select: ` |
||||
|
for { |
||||
|
fmt.Print(options) |
||||
|
|
||||
|
option, _ := newcommand.ReadString('\n') |
||||
|
option = strings.TrimSpace(option) |
||||
|
|
||||
|
switch option { |
||||
|
case "1": |
||||
|
fmt.Println("selected 1 - Store Pad (to IPFS and Git)") |
||||
|
option1() |
||||
|
break |
||||
|
case "2": |
||||
|
fmt.Println("selected 2 - IPFS hash to file") |
||||
|
option2() |
||||
|
break |
||||
|
case "0": |
||||
|
fmt.Println("selected 0 - exit cli") |
||||
|
os.Exit(3) |
||||
|
break |
||||
|
default: |
||||
|
fmt.Println("Invalid option") |
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
func option1() { |
||||
|
newcommand := bufio.NewReader(os.Stdin) |
||||
|
fmt.Print(" Enter the repo ID (name): ") |
||||
|
repoID, _ := newcommand.ReadString('\n') |
||||
|
repoID = strings.Replace(repoID, "\n", "", -1) |
||||
|
|
||||
|
newcommand = bufio.NewReader(os.Stdin) |
||||
|
fmt.Print(" Enter the pad link: ") |
||||
|
link, _ := newcommand.ReadString('\n') |
||||
|
link = strings.Replace(link, "\n", "", -1) |
||||
|
|
||||
|
newcommand = bufio.NewReader(os.Stdin) |
||||
|
fmt.Print(" Enter the subdirectory: ") |
||||
|
subdirectory, _ := newcommand.ReadString('\n') |
||||
|
subdirectory = strings.Replace(subdirectory, "\n", "", -1) |
||||
|
|
||||
|
newcommand = bufio.NewReader(os.Stdin) |
||||
|
fmt.Print(" Enter the pad Title: ") |
||||
|
title, _ := newcommand.ReadString('\n') |
||||
|
title = strings.Replace(title, "\n", "", -1) |
||||
|
|
||||
|
repo := padArchiver.OpenRepo(repoID) |
||||
|
|
||||
|
ipfsHash, err := repo.StorePad(link, subdirectory, title) |
||||
|
if err != nil { |
||||
|
color.Red(err.Error()) |
||||
|
} else { |
||||
|
fmt.Println("IPFS hash: " + ipfsHash) |
||||
|
color.Green(checkIcon + "Pad stored in IPFS and Git") |
||||
|
} |
||||
|
} |
||||
|
func option2() { |
||||
|
newcommand := bufio.NewReader(os.Stdin) |
||||
|
fmt.Print(" Enter the IPFS hash: ") |
||||
|
hash, _ := newcommand.ReadString('\n') |
||||
|
hash = strings.Replace(hash, "\n", "", -1) |
||||
|
err := padArchiver.IpfsGet(hash, hash+".md") |
||||
|
if err != nil { |
||||
|
color.Red(err.Error()) |
||||
|
} else { |
||||
|
|
||||
|
color.Green(checkIcon + "File downloaded from IPFS network") |
||||
|
fmt.Print("File stored in: ") |
||||
|
color.Blue(padArchiver.GettedPads + "/" + hash + ".md") |
||||
|
} |
||||
|
} |
@ -0,0 +1,52 @@ |
|||||
|
package padArchiver |
||||
|
|
||||
|
import ( |
||||
|
"os" |
||||
|
|
||||
|
"github.com/fatih/color" |
||||
|
) |
||||
|
|
||||
|
const Storage = "reposStorage" |
||||
|
|
||||
|
type Repo struct { |
||||
|
Dir string |
||||
|
} |
||||
|
|
||||
|
func OpenRepo(directory string) Repo { |
||||
|
//if not exist create the repos directory
|
||||
|
_ = os.Mkdir(Storage, os.ModePerm) |
||||
|
|
||||
|
var repo Repo |
||||
|
repo.Dir = Storage + "/" + directory |
||||
|
//create the repo directory
|
||||
|
_ = os.Mkdir(repo.Dir, os.ModePerm) |
||||
|
return repo |
||||
|
} |
||||
|
|
||||
|
func (repo *Repo) StorePad(link string, directory string, title string) (string, error) { |
||||
|
path, err := repo.GetPad(link, "md", directory, title) |
||||
|
if err != nil { |
||||
|
color.Red(err.Error()) |
||||
|
return "", err |
||||
|
} |
||||
|
|
||||
|
hash, err := IpfsAdd(path) |
||||
|
if err != nil { |
||||
|
color.Red(err.Error()) |
||||
|
return hash, err |
||||
|
} |
||||
|
|
||||
|
err = AddLineToFile(path, "IPFS hash of this document: "+hash) |
||||
|
if err != nil { |
||||
|
color.Red(err.Error()) |
||||
|
return hash, err |
||||
|
} |
||||
|
// TODO
|
||||
|
// err = repo.GitUpdate("update commit")
|
||||
|
// if err != nil {
|
||||
|
// color.Red(err.Error())
|
||||
|
// return hash, err
|
||||
|
// }
|
||||
|
|
||||
|
return hash, nil |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
package padArchiver |
||||
|
|
||||
|
import ( |
||||
|
"testing" |
||||
|
|
||||
|
"github.com/fatih/color" |
||||
|
) |
||||
|
|
||||
|
const checkIcon = "\xE2\x9C\x94 " |
||||
|
|
||||
|
func TestAddPad(t *testing.T) { |
||||
|
color.Blue("TestAddPad") |
||||
|
repo := OpenRepo("Repo01") |
||||
|
_, err := repo.StorePad("http://board.net/p/pad1", "Group1", "pad1") |
||||
|
|
||||
|
if err == nil { |
||||
|
color.Green(checkIcon + "checked AddPad") |
||||
|
} else { |
||||
|
color.Red(err.Error()) |
||||
|
t.Errorf("Error AddPad") |
||||
|
} |
||||
|
} |