mirror of
https://github.com/Luzifer/mediatimeline.git
synced 2024-11-08 14:50:08 +00:00
Allow triggering force-reload without cached ID
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
38ab270812
commit
5c38527dc0
3 changed files with 130 additions and 96 deletions
|
@ -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">×</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">×</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
12
http.go
|
@ -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
50
main.go
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue