Skip to content

Commit

Permalink
update: add json api with optional parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
abhijit-hota committed May 1, 2022
1 parent cfdcf15 commit ec976b7
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 63 deletions.
2 changes: 1 addition & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

func main() {
router := gin.Default()
router.POST("/add", handlers.SaveToDB)
router.POST("/add", handlers.AddBookmark)
router.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
Expand Down
14 changes: 14 additions & 0 deletions api/db/bookmark.model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package db

type Meta struct {
Title string `json:"title"`
Description string `json:"description"`
Favicon []byte `json:"favicon"`
}

type Bookmark struct {
Meta Meta `json:"meta"`
URL string `json:"url" binding:"required"`
Created int `json:"created"`
LastUpdated int `json:"last_updated"`
}
11 changes: 8 additions & 3 deletions api/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ func InitializeDB() (db *sql.DB) {
utils.Must(err)

_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS links (
CREATE TABLE IF NOT EXISTS bookmarks (
id INTEGER NOT NULL PRIMARY KEY,
title TEXT,
url TEXT,
meta INTEGER,
url TEXT,
created INTEGER,
last_updated INTEGER
);
CREATE TABLE IF NOT EXISTS meta (
title TEXT,
description TEXT,
favicon BLOB
)
`)
utils.Must(err)
Expand Down
1 change: 1 addition & 0 deletions api/handlers/get_bookmarks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package handlers
102 changes: 102 additions & 0 deletions api/handlers/save_bookmark.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package handlers

import (
DB "bingo/api/db"
"bingo/api/utils"
"database/sql"
"errors"
"io"
"log"
"net/http"
"regexp"
"strings"
"time"

"github.com/gin-gonic/gin"
)

var db *sql.DB = DB.GetDB()

func getMetadataFromURL(url string, availableMetadata DB.Meta) (meta *DB.Meta, err error) {

url = strings.TrimSpace(url)
if !(strings.HasPrefix(url, "https://") || strings.HasPrefix(url, "https://")) {
url = "https://" + url
}
// TODO check url before passing into http
res, err := http.Get(url)
if err != nil {
return nil, errors.New("Invalid URL.")
}

defer res.Body.Close()

hm := new(DB.Meta)
data, _ := io.ReadAll(res.Body)
headRegex := regexp.MustCompile("<head>((.|\n|\r\n)+)</head>")

head := string(headRegex.Find(data))
head = strings.ReplaceAll(head, "\n", "")

if availableMetadata.Title == "" {
titleRegex := regexp.MustCompile(`<title.*>(.+)<\/title>`)
metaTitleRegex := regexp.MustCompile(`<meta.*?property="og:title".*?content="(.+?)".*?\/?>`)
titleMatches := titleRegex.FindStringSubmatch(head)
if len(titleMatches) == 0 {
titleMatches = metaTitleRegex.FindStringSubmatch(head)
}

if len(titleMatches) == 0 {
hm.Title = ""
} else {
hm.Title = titleMatches[1]
}
} else {
hm.Title = availableMetadata.Title
}

if availableMetadata.Description == "" {

descriptionRegex := regexp.MustCompile(`<meta.*?(?:name="description"|property="og:description").*?content="(.*?)".*?\/>`)
descMatches := descriptionRegex.FindStringSubmatch(head)
if len(descMatches) == 0 {
hm.Description = ""
} else {
hm.Description = descMatches[1]
}
} else {
hm.Description = availableMetadata.Description
}
hm.Favicon = availableMetadata.Favicon
return hm, nil
}

func AddBookmark(ctx *gin.Context) {
var json DB.Bookmark
if err := ctx.ShouldBindJSON(&json); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
meta, err := getMetadataFromURL(json.URL, json.Meta)
statement, err := db.Prepare("INSERT INTO meta (title, description, favicon) VALUES (?, ?, ?)")
utils.Must(err)
defer statement.Close()
info, err := statement.Exec(meta.Title, meta.Description, meta.Favicon)
metaID, _ := info.LastInsertId()

if err != nil {
log.Println(err)
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid URL."})
return
}

statement, err = db.Prepare("INSERT INTO bookmarks (url, meta, created, last_updated) VALUES (?, ?, ?, ?)")
utils.Must(err)
defer statement.Close()

now := time.Now().Unix()
_, err = statement.Exec(json.URL, metaID, now, now)
utils.Must(err)

ctx.String(http.StatusOK, "Saved URL.")
}
58 changes: 0 additions & 58 deletions api/handlers/save_link.go

This file was deleted.

24 changes: 23 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,26 @@
- https://gioui.org/
- https://sciter.com/ embedded in Go
- A webapp embedded in Go using embed directive. See [this comment](https://www.reddit.com/r/golang/comments/lmvut7/comment/gnz8kct/)
- https://github.com/wailsapp/wails
- https://github.com/wailsapp/wails


## Todo

- [] Update /add route to support title, meta data, etc, auto fill missing data[1]
- [] 1) Auto fill missing data configuration
- [] Add support for tags
- [] tags table?
- [] Add /get route
- [] all
- [] filter by tags
- [] sort by date added/updated
- [] Auto label
- [] Save as PDF/HTML
- [] Import/Export
- [] Web frontend
- [] Sync
- [] Desktop frontend
- [] CLI frontend
- [] Phone frontend
- [] Browser extension

100 changes: 100 additions & 0 deletions temp/test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package main

import (
"encoding/json"
"net/http"
"net/url"
"regexp"
"strings"

"io"
)

func main() {

http.HandleFunc(`/read`, func(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set(`Content-Type`, `application/json`)

err := req.ParseForm()
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
json.NewEncoder(rw).Encode(map[string]string{"error": err.Error()})
return
}

link := req.FormValue(`link`)
if link == "" {
rw.WriteHeader(http.StatusBadRequest)
json.NewEncoder(rw).Encode(map[string]string{"error": `empty value of link`})
return
}

if _, err := url.Parse(link); err != nil {
rw.WriteHeader(http.StatusBadRequest)
json.NewEncoder(rw).Encode(map[string]string{"error": err.Error()})
return
}

resp, err := http.Get(link)
if err != nil {
//proxy status and err
rw.WriteHeader(resp.StatusCode)
json.NewEncoder(rw).Encode(map[string]string{"error": err.Error()})
return
}
defer resp.Body.Close()

meta := extract(resp.Body)
rw.WriteHeader(http.StatusOK)
json.NewEncoder(rw).Encode(meta)
return
})

// little help %)
println("call like: \n$ curl -XPOST 'http://localhost:4567/read' -d link='https://github.com/golang/go'")

err := http.ListenAndServe(`:4567`, nil)
if err != nil {
panic(err)
}

}

type HTMLMeta struct {
Title string `json:"title"`
Description string `json:"description"`
Favicon string `json:"favicon"`
}

func extract(resp io.Reader) *HTMLMeta {
hm := new(HTMLMeta)
data, _ := io.ReadAll(resp)
headRegex := regexp.MustCompile("<head>((.|\n|\r\n)+)</head>")

head := string(headRegex.Find(data))
head = strings.ReplaceAll(head, "\n", "")

titleRegex := regexp.MustCompile(`<title.*>(.+)<\/title>`)
metaTitleRegex := regexp.MustCompile(`<meta.*?property="og:title".*?content="(.+?)".*?\/?>`)
descriptionRegex := regexp.MustCompile(`<meta.*?(?:name="description"|property="og:description").*?content="(.*?)".*?\/>`)

descMatches := descriptionRegex.FindStringSubmatch(head)
titleMatches := titleRegex.FindStringSubmatch(head)
if len(titleMatches) == 0 {
titleMatches = metaTitleRegex.FindStringSubmatch(head)
}

if len(descMatches) == 0 {
hm.Description = ""
} else {
hm.Description = descMatches[1]
}

if len(titleMatches) == 0 {
hm.Title = ""
} else {
hm.Title = titleMatches[1]
}

return hm
}

0 comments on commit ec976b7

Please sign in to comment.