1
0
mirror of https://github.com/Luzifer/mediatimeline.git synced 2024-09-18 23:42:56 +00:00

Allow triggering force-reload without cached ID

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2019-04-24 00:00:28 +02:00
parent 38ab270812
commit 5c38527dc0
Signed by: luzifer
GPG Key ID: DC2729FDD34BE99E
3 changed files with 130 additions and 96 deletions

View File

@ -35,87 +35,97 @@
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-5">
<a class="navbar-brand" href="#">MediaTimeline Viewer</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</nav>
<div id="app">
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-5">
<a class="navbar-brand" href="#">MediaTimeline Viewer</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="container">
<div class="row">
<div class="col-lg-3 col-md-4 col-12" v-for="tweet in tweets" :key="tweet.id" v-if="tweet.images">
<div class="card mb-3">
<div :style="`background-image: url('${tweet.images[0].image}')`" class="card-img-top"></div>
<div class="card-body">
<div class="media">
<img :src="tweet.user.image" class="mr-2 user-image">
<div class="media-body">
<h6 class="mt-0 mb-0">{{ tweet.user.screen_name }}</h6>
<small>{{ moment(tweet.posted).format('lll') }}</small>
</div>
</div>
</div>
<div class="card-footer text-right">
<a class="btn btn-sm btn-secondary" :href="`https://twitter.com/${tweet.user.screen_name}/status/${tweet.id}`">
<i class="fas fa-link"></i>
</a>
<button class="btn btn-sm btn-secondary" @click="refetch(tweet)">
<i class="fas fa-sync"></i>
</button>
<button
:class="{ btn: true, 'btn-sm': true, 'btn-secondary': true, 'faved': tweet.favorited }"
@click="favourite(tweet)"
>
<i class="fas fa-star"></i>
</button>
<button class="btn btn-sm btn-secondary" @click="callModal(tweet)">
<i class="fas fa-search-plus"></i>
<span class="badge badge-light badge-pill" v-if="tweet.images.length > 1">{{ tweet.images.length }}</span>
</button>
</div>
</div>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="#" @click="triggerForceFetch"><i class="fas fa-sync"></i> Force reload</a>
</li>
</ul>
</div>
</nav>
</div>
<div class="container">
<div class="modal" tabindex="-1" role="dialog" v-if="modalTweet">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ modalTweet.user.screen_name }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<div id="carouselExampleControls" class="carousel slide">
<div class="carousel-inner">
<div :class="{ 'carousel-item': true, 'active': idx == 0 }" v-for="(image, idx) in modalTweet.images">
<img :src="image.image" class="d-block w-100">
<div class="col-lg-3 col-md-4 col-12" v-for="tweet in tweets" :key="tweet.id" v-if="tweet.images">
<div class="card mb-3">
<div :style="`background-image: url('${tweet.images[0].image}')`" class="card-img-top"></div>
<div class="card-body">
<div class="media">
<img :src="tweet.user.image" class="mr-2 user-image">
<div class="media-body">
<h6 class="mt-0 mb-0">{{ tweet.user.screen_name }}</h6>
<small>{{ moment(tweet.posted).format('lll') }}</small>
</div>
</div>
<a class="carousel-control-prev" href="#carouselExampleControls" role="button" data-slide="prev" v-if="modalTweet.images.length > 1">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carouselExampleControls" role="button" data-slide="next" v-if="modalTweet.images.length > 1">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
</div>
<div class="card-footer text-right">
<a class="btn btn-sm btn-secondary" :href="`https://twitter.com/${tweet.user.screen_name}/status/${tweet.id}`">
<i class="fas fa-link"></i>
</a>
<button class="btn btn-sm btn-secondary" @click="refetch(tweet)">
<i class="fas fa-sync"></i>
</button>
<button
:class="{ btn: true, 'btn-sm': true, 'btn-secondary': true, 'faved': tweet.favorited }"
@click="favourite(tweet)"
>
<i class="fas fa-star"></i>
</button>
<button class="btn btn-sm btn-secondary" @click="callModal(tweet)">
<i class="fas fa-search-plus"></i>
<span class="badge badge-light badge-pill" v-if="tweet.images.length > 1">{{ tweet.images.length }}</span>
</button>
</div>
</div>
</div>
</div>
<div class="modal" tabindex="-1" role="dialog" v-if="modalTweet">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ modalTweet.user.screen_name }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div id="carouselExampleControls" class="carousel slide">
<div class="carousel-inner">
<div :class="{ 'carousel-item': true, 'active': idx == 0 }" v-for="(image, idx) in modalTweet.images">
<img :src="image.image" class="d-block w-100">
</div>
</div>
<a class="carousel-control-prev" href="#carouselExampleControls" role="button" data-slide="prev" v-if="modalTweet.images.length > 1">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carouselExampleControls" role="button" data-slide="next" v-if="modalTweet.images.length > 1">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div> <!-- /.container -->
</div> <!-- /.container -->
</div> <!-- /#app -->
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
@ -155,7 +165,7 @@
modalTweet: null,
},
el: ".container",
el: "#app",
methods: {
callModal(tweet) {
@ -167,6 +177,7 @@
.post('/api/favourite', { id: tweet.id })
.then((res) => {
if (res.data.length === 0) {
this.refetch(tweet)
return
}
@ -188,9 +199,9 @@
.catch((err) => console.log(err))
},
refresh() {
refresh(forceReload = false) {
let apiURL = '/api/page' // By default query page 1
if (this.tweets.length > 0) {
if (this.tweets.length > 0 && !forceReload) {
apiURL = `/api/since?id=${this.tweets[0].id}`
}
@ -208,6 +219,15 @@
})
},
triggerForceFetch() {
axios
.post('/api/force-reload')
.then(() => {
window.setTimeout(() => this.refresh(true), 10000)
})
.catch((err) => console.log(err))
},
upsertTweets(data) {
let tweets = this.tweets

12
http.go
View File

@ -15,6 +15,7 @@ func init() {
http.HandleFunc("/api/since", handleNewest)
http.HandleFunc("/api/favourite", handleFavourite)
http.HandleFunc("/api/refresh", handleTweetRefresh)
http.HandleFunc("/api/force-reload", handleForceReload)
}
func handlePage(w http.ResponseWriter, r *http.Request) {
@ -129,3 +130,14 @@ func handleTweetRefresh(w http.ResponseWriter, r *http.Request) {
tweetResponse(w, tweets)
}
func handleForceReload(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "This needs to be POST", http.StatusBadRequest)
return
}
go loadAndStoreTweets(true)
w.WriteHeader(http.StatusNoContent)
}

50
main.go
View File

@ -63,39 +63,41 @@ func main() {
cfg.UserToken, cfg.UserSecret,
cfg.AppToken, cfg.AppSecret,
)
go loadAndStoreTweets()
go func() {
for t := time.NewTicker(time.Minute); true; <-t.C {
loadAndStoreTweets(false)
}
}()
http.Handle("/", http.FileServer(http.Dir(cfg.Frontend)))
http.ListenAndServe(cfg.Listen, nil)
}
func loadAndStoreTweets() {
for t := time.NewTicker(time.Minute); true; <-t.C {
params := url.Values{
"count": []string{"100"},
}
func loadAndStoreTweets(forceRefresh bool) {
params := url.Values{
"count": []string{"100"},
}
lastTweet := tweetStore.GetLastTweetID()
lastTweet := tweetStore.GetLastTweetID()
if lastTweet > 0 {
params.Set("since_id", strconv.FormatUint(lastTweet, 10))
}
if lastTweet > 0 && !forceRefresh {
params.Set("since_id", strconv.FormatUint(lastTweet, 10))
}
anacondaTweets, err := twitter.GetListTweetsBySlug(cfg.ListSlug, cfg.ListOwner, false, params)
if err != nil {
log.WithError(err).Error("Unable to fetch tweets")
continue
}
anacondaTweets, err := twitter.GetListTweetsBySlug(cfg.ListSlug, cfg.ListOwner, false, params)
if err != nil {
log.WithError(err).Error("Unable to fetch tweets")
return
}
tweets, err := convertTweetList(anacondaTweets, true)
if err != nil {
log.WithError(err).Error("Unable to parse tweets")
continue
}
tweets, err := convertTweetList(anacondaTweets, true)
if err != nil {
log.WithError(err).Error("Unable to parse tweets")
return
}
if err := tweetStore.StoreTweets(tweets); err != nil {
log.WithError(err).Error("Unable to store tweets")
continue
}
if err := tweetStore.StoreTweets(tweets); err != nil {
log.WithError(err).Error("Unable to store tweets")
return
}
}