Started work on Admin interface and websockets implementation
This commit is contained in:
parent
11c27edc23
commit
fc14acd76a
|
@ -1,15 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/net/websocket"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/robfig/cron"
|
"github.com/robfig/cron"
|
||||||
"gopkg.in/gin-gonic/gin.v1"
|
|
||||||
|
|
||||||
"git.1750studios.com/AniNite/SocialDragon/config"
|
"git.1750studios.com/AniNite/SocialDragon/config"
|
||||||
"git.1750studios.com/AniNite/SocialDragon/database"
|
"git.1750studios.com/AniNite/SocialDragon/database"
|
||||||
|
@ -25,13 +22,15 @@ func main() {
|
||||||
c := cron.New()
|
c := cron.New()
|
||||||
c.AddFunc("@every 30s", snapchat.LoadNewSnaps)
|
c.AddFunc("@every 30s", snapchat.LoadNewSnaps)
|
||||||
c.AddFunc("@every 30s", instagram.LoadNewInstas)
|
c.AddFunc("@every 30s", instagram.LoadNewInstas)
|
||||||
|
c.AddFunc("@every 5s", sendNewPicture)
|
||||||
c.Start()
|
c.Start()
|
||||||
|
|
||||||
go twitter.LoadNewTweets()
|
go twitter.LoadNewTweets()
|
||||||
|
go setupGin()
|
||||||
|
|
||||||
ch := make(chan os.Signal)
|
ch := make(chan os.Signal)
|
||||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||||
go setupGin()
|
|
||||||
<-ch
|
<-ch
|
||||||
|
|
||||||
twitter.Stop()
|
twitter.Stop()
|
||||||
|
@ -39,23 +38,17 @@ func main() {
|
||||||
|
|
||||||
func setupGin() {
|
func setupGin() {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
router.GET("/home", renderHomepage)
|
router.GET("/", renderHomepage)
|
||||||
router.GET("/ws", websocketEndpoint)
|
|
||||||
router.LoadHTMLGlob("templates/*")
|
|
||||||
router.Static("/static", "assets")
|
|
||||||
router.Run("127.0.0.1:8000")
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderHomepage(ctx *gin.Context) {
|
router.GET("/admin", func(c *gin.Context) { c.Redirect(301, "/admin/inbox") })
|
||||||
ctx.HTML(200, "index.html", gin.H{"title": "Main website"})
|
router.GET("/admin/inbox", renderAdminInbox)
|
||||||
}
|
router.GET("/admin/approved", renderAdminApproved)
|
||||||
|
router.GET("/admin/rejected", renderAdminRejected)
|
||||||
|
|
||||||
func websocketEndpoint(ctx *gin.Context) {
|
router.GET("/ws", func(c *gin.Context) { wsHandler(c.Writer, c.Request) })
|
||||||
handler := websocket.Handler(echoHandler)
|
|
||||||
handler.ServeHTTP(ctx.Writer, ctx.Request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func echoHandler(ws *websocket.Conn) {
|
router.LoadHTMLGlob(config.C.TemplatesDirectory + "/*")
|
||||||
websocket.Message.Send(ws, "Test Message from Server")
|
router.Static("/static", config.C.AssetsDirectory)
|
||||||
io.Copy(ws, ws)
|
router.Static(config.C.ContentWebDirectory, config.C.ContentDirectory)
|
||||||
|
router.Run(config.C.BindAddress)
|
||||||
}
|
}
|
||||||
|
|
54
socialdragon/socket.go
Normal file
54
socialdragon/socket.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.1750studios.com/AniNite/SocialDragon/database"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var wsupgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var sockets []*websocket.Conn
|
||||||
|
var lastID uint
|
||||||
|
|
||||||
|
func wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
conn, err := wsupgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to set websocket upgrade: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pos := len(sockets)
|
||||||
|
sockets = append(sockets, conn)
|
||||||
|
for {
|
||||||
|
_, _, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sockets = append(sockets[:pos], sockets[pos+1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendNewPicture() {
|
||||||
|
var ITs []database.Item
|
||||||
|
database.Db.Offset(lastID).Limit(1).Find(&ITs)
|
||||||
|
for i, socket := range sockets {
|
||||||
|
for _, IT := range ITs {
|
||||||
|
err := socket.WriteJSON(IT)
|
||||||
|
if IT.ID > lastID {
|
||||||
|
lastID = IT.ID
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
sockets = append(sockets[:i], sockets[i+1:]...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
socialdragon/templates/admin-inbox.html
Normal file
46
socialdragon/templates/admin-inbox.html
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
{{ template "header.html" . }}
|
||||||
|
<div class="row">
|
||||||
|
<div class="large-12 columns">
|
||||||
|
<h1>{{ .title }}</h1>
|
||||||
|
<div id="images" class="row small-up-2 medium-up-3 large-up-4">
|
||||||
|
{{ range .its }}
|
||||||
|
<div class="column column-block">
|
||||||
|
{{ if .IsVideo }}
|
||||||
|
<a class="thumbnail" data-toggle="{{ .ID }}-modal">
|
||||||
|
<strong>Video</strong>
|
||||||
|
</a>
|
||||||
|
{{ else }}
|
||||||
|
<a class="thumbnail" data-toggle="{{ .ID }}-modal">
|
||||||
|
<img alt="" src="{{ .Path }}" />
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<div id="{{ .ID }}-modal" class="full reveal" data-reveal>
|
||||||
|
<div class="row align-top">
|
||||||
|
<div class="small-10 columns">
|
||||||
|
{{ if .IsVideo }}
|
||||||
|
<video controls>
|
||||||
|
<source src="{{ .Path }}" />
|
||||||
|
</video>
|
||||||
|
{{ else }}
|
||||||
|
<img alt="" src="{{ .Path }}" />
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<div class="small-2 columns">
|
||||||
|
<a class="button success" href="javascript:approveMedia({{ .ID }})">Approve</a><br />
|
||||||
|
<a class="button alert" href="javascript:rejectMedia({{ .ID }})">Reject</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="close-button" data-close aria-label="Close reveal" type="button">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var site = "{{ .site }}";
|
||||||
|
var obj = document.getElementById("images");
|
||||||
|
</script>
|
||||||
|
{{ template "footer.html" . }}
|
|
@ -1,65 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html class="no-js" lang="en" dir="ltr">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Snapchat Wall Admin</title>
|
|
||||||
<meta http-equiv="refresh" content="30">
|
|
||||||
<link rel="stylesheet" href="/static/css/foundation.css">
|
|
||||||
<link rel="stylesheet" href="/static/css/app.css">
|
|
||||||
<script>
|
|
||||||
function httpGetAsync(theUrl, callback) {
|
|
||||||
var xmlHttp = new XMLHttpRequest();
|
|
||||||
xmlHttp.onreadystatechange = function() {
|
|
||||||
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
|
|
||||||
callback(xmlHttp.responseText);
|
|
||||||
}
|
|
||||||
xmlHttp.open("GET", theUrl, true); // true for asynchronous
|
|
||||||
xmlHttp.send(null);
|
|
||||||
}
|
|
||||||
function approveSnap(myself) {
|
|
||||||
function cb(success) {
|
|
||||||
if (success == "True") {
|
|
||||||
myself.parentNode.removeChild(myself);
|
|
||||||
} else {
|
|
||||||
alert("Failed!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
url = myself.src;
|
|
||||||
filename = url.substring(url.lastIndexOf('/')+1);
|
|
||||||
if (confirm("Approve this snap?")) {
|
|
||||||
httpGetAsync("/admin/snap/approve?filename=" + filename, cb);
|
|
||||||
} else {
|
|
||||||
httpGetAsync("/admin/snap/reject?filename=" + filename, cb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="row">
|
|
||||||
<div class="large-12 columns">
|
|
||||||
<h1>Snapchat Wall · Admin</h1>
|
|
||||||
<ul class="menu">
|
|
||||||
<li class="active"><a>Inbox</a></li>
|
|
||||||
<li><a>Approved</a></li>
|
|
||||||
<li><a>Rejected</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="large-12 columns">
|
|
||||||
<div class="callout">
|
|
||||||
<p>Click on snap to review:</p>
|
|
||||||
<img style="width: 30%; min-width: 15em;" onclick="approveSnap(this)" class="thumbnail" src="/snaps/inbox/IMAGE_HERE" alt="" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/static/js/vendor/jquery.js"></script>
|
|
||||||
<script src="/static/js/vendor/what-input.js"></script>
|
|
||||||
<script src="/static/js/vendor/foundation.js"></script>
|
|
||||||
<script src="/static/js/app.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
6
socialdragon/templates/footer.html
Normal file
6
socialdragon/templates/footer.html
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<script src="/static/js/vendor/jquery.js"></script>
|
||||||
|
<script src="/static/js/vendor/what-input.js"></script>
|
||||||
|
<script src="/static/js/vendor/foundation.js"></script>
|
||||||
|
<script src="/static/js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
21
socialdragon/templates/header.html
Normal file
21
socialdragon/templates/header.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html class="no-js" lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ .title }}</title>
|
||||||
|
<link rel="stylesheet" href="/static/css/foundation.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/app.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="top-bar">
|
||||||
|
<div class="top-bar-left">
|
||||||
|
<ul class="dropdown menu" data-dropdown-menu>
|
||||||
|
<li class="menu-text">SocialDragon – Admin</li>
|
||||||
|
<li><a href="/admin/inbox">Inbox</a></li>
|
||||||
|
<li><a href="/admin/approved">Approved</a></li>
|
||||||
|
<li><a href="/admin/rejected">Rejected</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -2,7 +2,7 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Snapchat Wall</title>
|
<title>{{.title}}</title>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
@import 'https://fonts.googleapis.com/css?family=Roboto';
|
@import 'https://fonts.googleapis.com/css?family=Roboto';
|
||||||
html, body {
|
html, body {
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
<div id="left">
|
<div id="left">
|
||||||
<div id="image">
|
<div id="image">
|
||||||
<center>
|
<center>
|
||||||
<img style="max-height: 100%; background: green; display: inline-block; vertical-align: middle;" id="snapwall_image" src="/snaps/approved/IMAGE_HERE" alt="" />
|
<img style="max-height: 100%; background: green; display: inline-block; vertical-align: middle;" id="snapwall_image" src="" alt="" />
|
||||||
</center>
|
</center>
|
||||||
</div>
|
</div>
|
||||||
</div><!--
|
</div><!--
|
||||||
|
@ -78,14 +78,15 @@
|
||||||
<div style="clear:both"></div>
|
<div style="clear:both"></div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var exampleSocket = new WebSocket("ws://127.0.0.1:8000/ws");
|
var exampleSocket = new WebSocket("ws://192.168.2.108:8000/ws");
|
||||||
exampleSocket.onopen = function (event) {
|
exampleSocket.onopen = function (event) {
|
||||||
console.log("WS: Connection open!");
|
console.log("WS: Connection open!");
|
||||||
console.log("Proto: " + exampleSocket.protocol);
|
console.log("Proto: " + exampleSocket.protocol);
|
||||||
exampleSocket.send("Here's some text that the server is urgently awaiting!");
|
exampleSocket.send("Here's some text that the server is urgently awaiting!");
|
||||||
};
|
};
|
||||||
exampleSocket.onmessage = function (event) {
|
exampleSocket.onmessage = function (event) {
|
||||||
console.log("WS Message:");
|
IT = JSON.parse(event.data);
|
||||||
|
document.getElementById("snapwall_image").src = IT["Path"];
|
||||||
console.log(event.data);
|
console.log(event.data);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
DatabaseConnection = ""
|
|
||||||
BindAddress = ""
|
|
||||||
AssetsDirectory = ""
|
|
||||||
TemplatesDirectory = ""
|
|
||||||
ContentDirectory = ""
|
|
||||||
ContentWebDirectory = ""
|
|
||||||
|
|
||||||
[Snapchat]
|
|
||||||
ApiBase = ""
|
|
||||||
UserAgent = ""
|
|
||||||
UserName = ""
|
|
||||||
[Snapchat.GetConversations]
|
|
||||||
Uuid = ""
|
|
||||||
ClientAuthToken = ""
|
|
||||||
RequestToken = ""
|
|
||||||
Timestamp = ""
|
|
||||||
[Snapchat.GetBlob]
|
|
||||||
Uuid = ""
|
|
||||||
ClientAuthToken = ""
|
|
||||||
RequestToken = ""
|
|
||||||
Timestamp = ""
|
|
||||||
[Snapchat.MarkAsSeen]
|
|
||||||
Uuid = ""
|
|
||||||
ClientAuthToken = ""
|
|
||||||
RequestToken = ""
|
|
||||||
Timestamp = ""
|
|
||||||
|
|
||||||
[Twitter]
|
|
||||||
ConsumerKey = ""
|
|
||||||
ConsumerSecret = ""
|
|
||||||
OAuthToken = ""
|
|
||||||
OAuthTokenSecret = ""
|
|
||||||
Filter = ""
|
|
35
socialdragon/webapp.go
Normal file
35
socialdragon/webapp.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.1750studios.com/AniNite/SocialDragon/database"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wall
|
||||||
|
func renderHomepage(c *gin.Context) {
|
||||||
|
c.HTML(200, "index.html", gin.H{"title": "SocialDragon"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admin
|
||||||
|
func renderAdminInbox(c *gin.Context) {
|
||||||
|
var ITs []database.Item
|
||||||
|
database.Db.Order("created_at", false).Find(&ITs, "state = ?", database.Inbox)
|
||||||
|
log.Printf("%+v", ITs)
|
||||||
|
c.HTML(200, "admin-inbox.html", gin.H{"its": ITs, "title": "SocialDragon – Admin – Inbox", "site": "admin-inbox"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderAdminApproved(c *gin.Context) {
|
||||||
|
var ITs []database.Item
|
||||||
|
database.Db.Order("updated_at", true).Find(&ITs, "state = ?", database.Approved)
|
||||||
|
log.Printf("%+v", ITs)
|
||||||
|
c.HTML(200, "admin-approved.html", gin.H{"its": ITs, "title": "SocialDragon – Admin – Approved", "site": "admin-approved"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderAdminRejected(c *gin.Context) {
|
||||||
|
var ITs []database.Item
|
||||||
|
database.Db.Order("updated_at", true).Find(&ITs, "state = ?", database.Rejected)
|
||||||
|
log.Printf("%+v", ITs)
|
||||||
|
c.HTML(200, "admin-rejected.html", gin.H{"its": ITs, "title": "SocialDragon – Admin – Rejected", "site": "admin-rejected"})
|
||||||
|
}
|
Loading…
Reference in a new issue