Currently we have some license issues. We are working on it.

Commit 071faf67 authored by zauberstuhl's avatar zauberstuhl
Browse files

Initial commit

parents
config.yml
*.db
vendor/
FROM golang:1.12
RUN go get -u github.com/golang/dep/cmd/dep
ADD . /go/src/build
WORKDIR /go/src/build
RUN make compile
FROM debian:jessie
RUN apt-get update && apt-get install -y ca-certificates
RUN apt-get clean && apt-get autoclean
RUN adduser --disabled-password app
ADD . /home/app
COPY --from=0 /go/src/build/unmappd /home/app/unmappd
RUN chmod +x /home/app/unmappd
WORKDIR /home/app
RUN cp config.yml.example config.yml
RUN rm -rf vendor *.db
EXPOSE 8080
ENTRYPOINT ["/home/app/unmappd"]
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd"
name = "github.com/fsnotify/fsnotify"
packages = ["."]
pruneopts = "UT"
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
version = "v1.4.7"
[[projects]]
digest = "1:c0d19ab64b32ce9fe5cf4ddceba78d5bc9807f0016db6b1183599da3dcc24d10"
name = "github.com/hashicorp/hcl"
packages = [
".",
"hcl/ast",
"hcl/parser",
"hcl/printer",
"hcl/scanner",
"hcl/strconv",
"hcl/token",
"json/parser",
"json/scanner",
"json/token",
]
pruneopts = "UT"
revision = "8cb6e5b959231cc1119e43259c4a608f9c51a241"
version = "v1.0.0"
[[projects]]
digest = "1:27f44b9a8c2995c35ec25a9948ffa96f997f8eedbc5bb34838f10dcf620aaee3"
name = "github.com/jinzhu/gorm"
packages = [
".",
"dialects/sqlite",
]
pruneopts = "UT"
revision = "836fb2c19d84dac7b0272958dfb9af7cf0d0ade4"
version = "v1.9.10"
[[projects]]
digest = "1:01ed62f8f4f574d8aff1d88caee113700a2b44c42351943fa73cc1808f736a50"
name = "github.com/jinzhu/inflection"
packages = ["."]
pruneopts = "UT"
revision = "f5c5f50e6090ae76a29240b61ae2a90dd810112e"
version = "v1.0.0"
[[projects]]
digest = "1:5a0ef768465592efca0412f7e838cdc0826712f8447e70e6ccc52eb441e9ab13"
name = "github.com/magiconair/properties"
packages = ["."]
pruneopts = "UT"
revision = "de8848e004dd33dc07a2947b3d76f618a7fc7ef1"
version = "v1.8.1"
[[projects]]
digest = "1:4a49346ca45376a2bba679ca0e83bec949d780d4e927931317904bad482943ec"
name = "github.com/mattn/go-sqlite3"
packages = ["."]
pruneopts = "UT"
revision = "c7c4067b79cc51e6dfdcef5c702e74b1e0fa7c75"
version = "v1.10.0"
[[projects]]
digest = "1:6ff1026b8d873d074ddec82ad60bb0eb537dc013f456ef26b63d30beba2f99e7"
name = "github.com/microcosm-cc/bluemonday"
packages = ["."]
pruneopts = "UT"
revision = "506f3da9b7c86d737e91f16b7431df8635871552"
version = "v1.0.2"
[[projects]]
digest = "1:53bc4cd4914cd7cd52139990d5170d6dc99067ae31c56530621b18b35fc30318"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
pruneopts = "UT"
revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe"
version = "v1.1.2"
[[projects]]
digest = "1:93131d8002d7025da13582877c32d1fc302486775a1b06f62241741006428c5e"
name = "github.com/pelletier/go-toml"
packages = ["."]
pruneopts = "UT"
revision = "728039f679cbcd4f6a54e080d2219a4c4928c546"
version = "v1.4.0"
[[projects]]
digest = "1:b778880d47d8544b11cd82b0d9731f9669d653489b400d0b5899b8a20934e7b4"
name = "github.com/robfig/cron"
packages = ["."]
pruneopts = "UT"
revision = "45fbe1491cdd47d74d1bf1396286d67faee8b8b5"
version = "v3.0.0"
[[projects]]
digest = "1:bb495ec276ab82d3dd08504bbc0594a65de8c3b22c6f2aaa92d05b73fbf3a82e"
name = "github.com/spf13/afero"
packages = [
".",
"mem",
]
pruneopts = "UT"
revision = "588a75ec4f32903aa5e39a2619ba6a4631e28424"
version = "v1.2.2"
[[projects]]
digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc"
name = "github.com/spf13/cast"
packages = ["."]
pruneopts = "UT"
revision = "8c9545af88b134710ab1cd196795e7f2388358d7"
version = "v1.3.0"
[[projects]]
digest = "1:1b753ec16506f5864d26a28b43703c58831255059644351bbcb019b843950900"
name = "github.com/spf13/jwalterweatherman"
packages = ["."]
pruneopts = "UT"
revision = "94f6ae3ed3bceceafa716478c5fbf8d29ca601a1"
version = "v1.1.0"
[[projects]]
digest = "1:c1b1102241e7f645bc8e0c22ae352e8f0dc6484b6cb4d132fa9f24174e0119e2"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = "UT"
revision = "298182f68c66c05229eb03ac171abe6e309ee79a"
version = "v1.0.3"
[[projects]]
digest = "1:11118bd196646c6515fea3d6c43f66162833c6ae4939bfb229b9956d91c6cf17"
name = "github.com/spf13/viper"
packages = ["."]
pruneopts = "UT"
revision = "b5bf975e5823809fb22c7644d008757f78a4259e"
version = "v1.4.0"
[[projects]]
branch = "master"
digest = "1:d7b978a787736537d0ad2f84c0e8b75b05c2febef0537198e28edf1cf2768afd"
name = "golang.org/x/net"
packages = [
"html",
"html/atom",
]
pruneopts = "UT"
revision = "da137c7871d730100384dbcf36e6f8fa493aef5b"
[[projects]]
branch = "master"
digest = "1:5632b0c4d972da51b5914f09fc5c1a8535e9d8d5d937e95ef83c423a0dd67f13"
name = "golang.org/x/sys"
packages = ["unix"]
pruneopts = "UT"
revision = "fae7ac547cb717d141c433a2a173315e216b64c4"
[[projects]]
digest = "1:1093f2eb4b344996604f7d8b29a16c5b22ab9e1b25652140d3fede39f640d5cd"
name = "golang.org/x/text"
packages = [
"internal/gen",
"internal/triegen",
"internal/ucd",
"transform",
"unicode/cldr",
"unicode/norm",
]
pruneopts = "UT"
revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
version = "v0.3.2"
[[projects]]
digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = "UT"
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
version = "v2.2.2"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/jinzhu/gorm",
"github.com/jinzhu/gorm/dialects/sqlite",
"github.com/microcosm-cc/bluemonday",
"github.com/robfig/cron",
"github.com/spf13/viper",
]
solver-name = "gps-cdcl"
solver-version = 1
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/jinzhu/gorm"
version = "1.9.10"
[[constraint]]
name = "github.com/robfig/cron"
version = "3.0.0"
[[constraint]]
name = "github.com/spf13/viper"
version = "1.4.0"
[prune]
go-tests = true
unused-packages = true
This diff is collapsed.
compile:
dep ensure
go build -o unmappd
build:
docker build -t zauberstuhl/unmappd:latest .
# Unmappd - map globally
## Build
It requires `make` and docker to build the app. Then simply execute:
make build
/*
* Unmappd - Visualize your Untappd checkins
* Copyright (C) 2019 Lukas Matt <lukas@matt.wf>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (
"time"
"net/http"
"io/ioutil"
"encoding/json"
"fmt"
"log"
"github.com/spf13/viper"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
const rootUrl = "https://api.untappd.com"
type Uresp struct {
Response struct {
Pagination struct {
Max_id int
}
Checkins struct {
Items []struct {
Checkin_id uint
Created_at string
Venue interface{}
}
}
}
}
type Uauthresp struct {
Response struct {
Access_token string
}
}
type Uinfo struct {
Response struct {
User struct {
User_name string
}
}
}
func authHandler(w http.ResponseWriter, r *http.Request) {
db, err := openDatabase()
if err != nil {
log.Print(err)
http.Redirect(w, r, "/", 301)
return
}
defer db.Close()
userParam := viper.GetString("nginxRewrite")
if userParam == "" {
userParam = "/?user=%s"
}
if codes, ok := r.URL.Query()["code"]; ok {
token, err := fetch_auth(codes[0])
if err != nil {
log.Print("Cannot fetch auth token: ", err)
http.Redirect(w, r, "/", 301)
return
}
username, err := fetch_username(token)
if err != nil || username == "" {
log.Print("Cannot fetch user info: ", err)
http.Redirect(w, r, "/", 301)
return
}
err = db.First(&User{}, "username = ?", username).Error
if err == nil {
http.Redirect(w, r,
fmt.Sprintf(userParam, username), 301)
return
}
var user = User{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Username: username,
Token: token,
}
err = db.Create(&user).Error
if err != nil {
log.Print("Cannot create user: ", err)
http.Redirect(w, r, "/", 301)
return
}
http.Redirect(w, r,
fmt.Sprintf(userParam, user.Username), 301)
return
}
http.Redirect(w, r, "/", 301)
}
func fetch_auth(code string) (string, error) {
var uauth Uauthresp
return uauth.Response.Access_token, get(fmt.Sprintf(
"https://untappd.com/oauth/authorize/?client_id=%s&client_secret=%s&response_type=code&redirect_url=%s&code=%s",
CLIENTID, CLIENTSECRET, REDIRECTURL, code), &uauth)
}
func fetch_username(token string) (string, error) {
var info Uinfo
url := fmt.Sprintf(
"%s/v4/user/info/?access_token=%s", rootUrl, token)
return info.Response.User.User_name, get(url, &info)
}
func get(url string, v interface{}) error {
resp, err := http.Get(url)
if err != nil {
log.Print("http.Get", err)
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Print("ioutil.ReadAll", err)
return err
}
if viper.GetBool("debug") {
log.Printf("get url body: %s\n", string(body))
}
return json.Unmarshal(body, v)
}
#nginxRewrite: "/@%s"
redirectUrl: http://localhost:8080
clientID: 1234
clientSecret: 123456789
mapboxKey: 123456789
debug: false
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Unmappd - map globally</title>
<script src="/assets/vendor/js/jquery-3.3.1.slim.min.js"></script>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="/assets/vendor/css/control.playback.css"/>
<link rel="stylesheet" href="/assets/vendor/css/bootstrap.min.css"/>
<link rel="stylesheet" href="/assets/vendor/css/leaflet.css"/>
<style>
html, body {
height: 100%;
}
#mapid {
height: calc(100% - 56px);
height: -o-calc(100% - 56px);
height: -webkit-calc(100% - 56px);
height: -moz-calc(100% - 56px);
}
</style>
</head>
<body>
<header>
<div class="collapse bg-dark" id="navbarHeader">
<div class="container">
<div class="row">
<div class="col-sm-8 col-md-7 py-4">
<h4 class="text-white">About</h4>
<p class="text-muted">The idea was born while drinking a lot of delicious beers in Helsinki</p>
</div>
<div class="col-sm-4 offset-md-1 py-4">
<h4 class="text-white">Details</h4>
<ul class="list-unstyled">
<li><a href="https://git.feneas.org/zauberstuhl/unmappd" class="text-white">Fork me</a></li>
<li><a href="mailto:lukas@matt.wf" class="text-white">Contact me</a></li>
</ul>
</div>
</div>
</div>
</div>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#" data-toggle="collapse" data-target="#navbarHeader">
<strong>Unmappd</strong>&nbsp;-&nbsp;map globally
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ml-auto">
{{if not .UserOK}}
<li class="nav-item">
<a class="nav-link" href="https://untappd.com/oauth/authenticate?client_id=
{{- .Viper.GetString "clientID"}}&response_type=code&redirect_url=
{{- .Viper.GetString "redirectUrl"}}">Login &amp; pull</a>
</li>
{{else}}
<li class="nav-item">
<script>
function playback() {
var href = window.location.href.replace(/#.*/g,'');
if (href.indexOf("&playback") > -1) {
window.location.href = href.replace('&playback','');
return;
}
window.location.href = href + "&playback";
}
</script>
<a class="nav-link" href="#" onClick="playback()">Playback</a>
</li>
{{end}}
</ul>
</div>
</nav>
</header>
{{if .Locations}}
<div id="mapid"></div>
<script src="/assets/vendor/js/leaflet.js"></script>
<script src="/assets/vendor/js/control.trackplayback.js"></script>
<script src="/assets/vendor/js/leaflet.trackplayback.js"></script>
<script>
var mymap = L.map('mapid').setView([49.015,8.269], 2);
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}', {
attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
maxZoom: 18,
id: 'mapbox.streets',
accessToken: '{{.Viper.GetString "mapboxKey"}}'
}).addTo(mymap);
{{if .PlaybackOK}}
var trackplayback = new L.TrackPlayBack({{.Locations.LatLngTimeJSON}}, mymap, {
clockOptions: {
// the default speed
// caculate method: fpstime * Math.pow(2, speed - 1)
// fpstime is the two frame time difference
speed: 20,
// the max speed
maxSpeed: 65
},
// trackPoint options
trackPointOptions: {
isDraw: true,
useCanvas: false,
stroke: false,
color: '#ef0300',
fill: true,
fillColor: '#ef0300',
opacity: 1,
radius: 5
},
trackLineOptions: {
isDraw: false,
},
targetOptions: {
useImg: false,
width: 8,
height: 8,
color: '#000',
fillColor: '#2881c3'
}
});
var trackplaybackControl = L.trackplaybackcontrol(trackplayback);
trackplaybackControl.addTo(mymap);
// remove checkbox
$(".trackplayback-checkbox").hide();
{{else}}
{{range .Locations}}
var marker = L.marker([{{.Lat}}, {{.Lng}}]).addTo(mymap);
marker.bindPopup(`
<table>
<tr>
<td><img src="{{.Icon}}" alt="{{.Name}}"/></td>
<td><b>{{.Name}}</b></td>
</tr>
<tr>
<td class="text-center pt-2" colspan="2">
Link: <a href="{{.Url}}">{{.Url}}</a><br>
Category: {{.Category}}
</td>
</tr>
</table>
`);
{{end}}
{{end}}
</script>
{{else}}
<main role="main">
<section class="mt-4 text-center">
<div class="container">
<h1 class="jumbotron-heading">Unmappd - Your beer map</h1>
{{if .UserOK}}
<p class="lead text-muted">No data is available for this user! Please login at least once.</p>
<form method="GET" action="https://untappd.com/oauth/authenticate">
<input type="hidden" name="client_id" value="{{.Viper.GetString "clientID"}}"/>
<input type="hidden" name="response_type" value="code"/>
<input type="hidden" name="redirect_url" value="{{.Viper.GetString "redirectUrl"}}"/>
<button class="btn btn-primary" type="submit">Login &amp; pull</button>
</form>
{{else}}
<p class="lead text-muted">Login and display all venues you checked in via Untappd</p>
<p class="text-muted">Latest users:</p>
<ul class="list-group list-group-flush">
{{range .LatestUsers}}
<li class="list-group-item"><a href="{{rwNginx .Username}}">{{.Username}}</a></li>
{{end}}
</ul>
{{end}}
</div>
</section>
</main>
{{end}}
<script src="/assets/vendor/js/popper.min.js"></script>
<script src="/assets/vendor/js/bootstrap.min.js"></script>
</body>
</html>
/*
* Unmappd - Visualize your Untappd checkins
* Copyright (C) 2019 Lukas Matt <lukas@matt.wf>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.