v35: Add git commit/push to deploy script
This commit is contained in:
+5
-3
@@ -134,6 +134,7 @@ func (c *Crawler) handleAPIDomains(w http.ResponseWriter, r *http.Request) {
|
||||
Type string `json:"type,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
PublishStatus string `json:"publish_status,omitempty"`
|
||||
Language string `json:"language,omitempty"`
|
||||
}
|
||||
|
||||
type DomainInfo struct {
|
||||
@@ -162,7 +163,7 @@ func (c *Crawler) handleAPIDomains(w http.ResponseWriter, r *http.Request) {
|
||||
// Now get feeds for these domains
|
||||
if len(hosts) > 0 {
|
||||
feedRows, err := c.db.Query(`
|
||||
SELECT source_host, url, title, type, status, publish_status
|
||||
SELECT source_host, url, title, type, status, publish_status, language
|
||||
FROM feeds
|
||||
WHERE source_host = ANY($1)
|
||||
ORDER BY source_host, url
|
||||
@@ -173,14 +174,15 @@ func (c *Crawler) handleAPIDomains(w http.ResponseWriter, r *http.Request) {
|
||||
for feedRows.Next() {
|
||||
var host string
|
||||
var f FeedInfo
|
||||
var title, feedType, status, publishStatus *string
|
||||
if err := feedRows.Scan(&host, &f.URL, &title, &feedType, &status, &publishStatus); err != nil {
|
||||
var title, feedType, status, publishStatus, language *string
|
||||
if err := feedRows.Scan(&host, &f.URL, &title, &feedType, &status, &publishStatus, &language); err != nil {
|
||||
continue
|
||||
}
|
||||
f.Title = StringValue(title)
|
||||
f.Type = StringValue(feedType)
|
||||
f.Status = StringValue(status)
|
||||
f.PublishStatus = StringValue(publishStatus)
|
||||
f.Language = StringValue(language)
|
||||
feedsByHost[host] = append(feedsByHost[host], f)
|
||||
}
|
||||
// Attach feeds to domains
|
||||
|
||||
+24
-16
@@ -7,6 +7,7 @@ import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/haileyok/atproto-oauth-golang/helpers"
|
||||
@@ -64,14 +65,29 @@ func (m *OAuthManager) HandleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
// If handle is provided, start OAuth flow
|
||||
handle := r.URL.Query().Get("handle")
|
||||
if handle != "" {
|
||||
// Save handle to cookie for prefill on next visit
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "last_handle",
|
||||
Value: handle,
|
||||
Path: "/",
|
||||
MaxAge: 86400 * 365, // 1 year
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
m.startOAuthFlow(w, r, handle)
|
||||
return
|
||||
}
|
||||
|
||||
// Get last handle from cookie for prefill
|
||||
lastHandle := ""
|
||||
if cookie, err := r.Cookie("last_handle"); err == nil {
|
||||
lastHandle = cookie.Value
|
||||
}
|
||||
|
||||
// Serve login page
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
tmpl := template.Must(template.New("login").Parse(loginPageHTML))
|
||||
tmpl.Execute(w, nil)
|
||||
tmpl.Execute(w, map[string]string{"LastHandle": lastHandle})
|
||||
}
|
||||
|
||||
// startOAuthFlow initiates the OAuth flow for a given handle
|
||||
@@ -79,6 +95,11 @@ func (m *OAuthManager) startOAuthFlow(w http.ResponseWriter, r *http.Request, ha
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Auto-append .bsky.social if handle has no dots
|
||||
if !strings.Contains(handle, ".") {
|
||||
handle = handle + ".bsky.social"
|
||||
}
|
||||
|
||||
fmt.Printf("OAuth: starting flow for handle: %s\n", handle)
|
||||
|
||||
// Resolve handle to DID
|
||||
@@ -168,20 +189,7 @@ func (m *OAuthManager) startOAuthFlow(w http.ResponseWriter, r *http.Request, ha
|
||||
|
||||
fmt.Printf("OAuth: redirecting to: %s\n", authURL.String())
|
||||
|
||||
// Use JavaScript redirect to preserve browser security headers
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
fmt.Fprintf(w, `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to authorization server...</p>
|
||||
<script>window.location.href = %q;</script>
|
||||
<noscript><a href="%s">Click here to continue</a></noscript>
|
||||
</body>
|
||||
</html>`, authURL.String(), authURL.String())
|
||||
http.Redirect(w, r, authURL.String(), http.StatusFound)
|
||||
}
|
||||
|
||||
// HandleCallback handles the OAuth callback
|
||||
@@ -453,7 +461,7 @@ const loginPageHTML = `<!DOCTYPE html>
|
||||
<form id="loginForm" action="/auth/login" method="get">
|
||||
<div class="form-group">
|
||||
<label for="handle">Bluesky Handle</label>
|
||||
<input type="text" id="handle" name="handle" placeholder="handle.bsky.social" required autofocus>
|
||||
<input type="text" id="handle" name="handle" placeholder="username or full.handle" value="{{.LastHandle}}" required autofocus>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="login-btn" id="loginBtn">
|
||||
|
||||
+16
-2
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
# Deploy script - increments version and relaunches container
|
||||
# Usage: ./scripts/deploy.sh
|
||||
# Deploy script - increments version, commits, pushes, and relaunches container
|
||||
# Usage: ./scripts/deploy.sh [optional commit message]
|
||||
|
||||
set -e
|
||||
|
||||
@@ -22,6 +22,20 @@ sed -i '' "s/>v${CURRENT}</>v${NEW}</" templates.go
|
||||
|
||||
echo "Version: v${CURRENT} -> v${NEW}"
|
||||
|
||||
# Build commit message
|
||||
if [ -n "$1" ]; then
|
||||
COMMIT_MSG="v${NEW}: $1"
|
||||
else
|
||||
COMMIT_MSG="v${NEW}"
|
||||
fi
|
||||
|
||||
# Commit and push
|
||||
git add -A
|
||||
git commit -m "$COMMIT_MSG"
|
||||
git push
|
||||
|
||||
echo "Committed: $COMMIT_MSG"
|
||||
|
||||
# Rebuild and relaunch
|
||||
docker compose up -d --build
|
||||
|
||||
|
||||
+20
-6
@@ -131,6 +131,10 @@ function initDashboard() {
|
||||
|
||||
html += `<div class="feed-row" style="display: flex; align-items: center; padding: 4px 0; font-size: 12px;">`;
|
||||
|
||||
// Language indicator (fixed width)
|
||||
const lang = f.language || '';
|
||||
html += `<span style="display: inline-block; width: 24px; color: #666; font-size: 10px; font-family: monospace; text-align: center;">${escapeHtml(lang)}</span>`;
|
||||
|
||||
// Feed status buttons
|
||||
html += renderStatusBtns(feedStatus, 'feed', f.url, f.status);
|
||||
|
||||
@@ -141,7 +145,7 @@ function initDashboard() {
|
||||
feedPath = urlObj.pathname + urlObj.search;
|
||||
} catch (e) {}
|
||||
const displayText = f.title ? `${feedPath} - ${f.title}` : feedPath;
|
||||
html += `<span class="feed-title" style="color: #888; cursor: pointer; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="${escapeHtml(f.url)}">${escapeHtml(displayText)}</span>`;
|
||||
html += `<span class="feed-title" style="color: #fff; cursor: pointer; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="${escapeHtml(f.url)}">${escapeHtml(displayText)}</span>`;
|
||||
|
||||
// Feed type
|
||||
if (f.type) {
|
||||
@@ -170,6 +174,10 @@ function initDashboard() {
|
||||
|
||||
html += `<div class="feed-row" style="display: flex; align-items: flex-start; padding: 8px 10px; border-bottom: 1px solid #202020;">`;
|
||||
|
||||
// Language indicator (fixed width)
|
||||
const lang = f.language || '';
|
||||
html += `<span style="display: inline-block; width: 24px; color: #666; font-size: 10px; font-family: monospace; text-align: center; padding-top: 4px;">${escapeHtml(lang)}</span>`;
|
||||
|
||||
// Status buttons (pass/hold/skip + fail indicator if errors)
|
||||
html += `<div style="padding-top: 2px;">${renderStatusBtns(status, 'feed', f.url, f.status)}</div>`;
|
||||
|
||||
@@ -357,7 +365,7 @@ function initDashboard() {
|
||||
|
||||
// Description
|
||||
if (info.description) {
|
||||
html += `<div style="color: #aaa; margin-bottom: 10px; font-size: 0.9em;">${escapeHtml(info.description)}</div>`;
|
||||
html += `<div style="color: #fff; margin-bottom: 10px; font-size: 0.9em;">${escapeHtml(info.description)}</div>`;
|
||||
}
|
||||
|
||||
// Info row
|
||||
@@ -384,7 +392,7 @@ function initDashboard() {
|
||||
if (item.title && item.link) {
|
||||
html += `<a href="${escapeHtml(item.link)}" target="_blank" style="color: #0af; text-decoration: none;">${escapeHtml(item.title)}</a>`;
|
||||
} else if (item.title) {
|
||||
html += `<span style="color: #ccc;">${escapeHtml(item.title)}</span>`;
|
||||
html += `<span style="color: #fff;">${escapeHtml(item.title)}</span>`;
|
||||
}
|
||||
if (item.pub_date) {
|
||||
const date = new Date(item.pub_date);
|
||||
@@ -438,6 +446,7 @@ function initDashboard() {
|
||||
}
|
||||
|
||||
// Show all domains view
|
||||
// statusFilter can be: 'hold', 'pass', 'skip', 'fail', or 'feeds' (special: has_feeds=true)
|
||||
async function showDomains(statusFilter = null) {
|
||||
currentView = 'domains';
|
||||
currentDomain = null;
|
||||
@@ -466,7 +475,11 @@ function initDashboard() {
|
||||
async function loadMore() {
|
||||
try {
|
||||
let url = `/api/domains?limit=${limit}&offset=${offset}&sort=alpha`;
|
||||
if (statusFilter) url += `&status=${statusFilter}`;
|
||||
if (statusFilter === 'feeds') {
|
||||
url += `&has_feeds=true`;
|
||||
} else if (statusFilter) {
|
||||
url += `&status=${statusFilter}`;
|
||||
}
|
||||
|
||||
const resp = await fetch(url);
|
||||
const domains = await resp.json();
|
||||
@@ -705,7 +718,7 @@ function initDashboard() {
|
||||
|
||||
// Description
|
||||
if (info.description) {
|
||||
html += `<div style="color: #aaa; margin-bottom: 15px;">${escapeHtml(info.description)}</div>`;
|
||||
html += `<div style="color: #fff; margin-bottom: 15px;">${escapeHtml(info.description)}</div>`;
|
||||
}
|
||||
|
||||
// Info table
|
||||
@@ -734,7 +747,7 @@ function initDashboard() {
|
||||
if (item.title && item.link) {
|
||||
html += `<a href="${escapeHtml(item.link)}" target="_blank" style="color: #0af; text-decoration: none;">${escapeHtml(item.title)}</a>`;
|
||||
} else if (item.title) {
|
||||
html += `<span style="color: #ccc;">${escapeHtml(item.title)}</span>`;
|
||||
html += `<span style="color: #fff;">${escapeHtml(item.title)}</span>`;
|
||||
}
|
||||
if (item.pub_date) {
|
||||
const date = new Date(item.pub_date);
|
||||
@@ -864,6 +877,7 @@ function initDashboard() {
|
||||
<div style="margin-bottom: 10px;"><span style="color: #0af;">/domains</span> - List all domains</div>
|
||||
<div style="margin-bottom: 10px;"><span style="color: #0af;">/feeds</span> - List all feeds</div>
|
||||
<div style="margin-bottom: 10px;"><span style="color: #0af;">d:hold d:pass d:skip d:fail</span> - Filter domains by status</div>
|
||||
<div style="margin-bottom: 10px;"><span style="color: #0af;">d:feeds</span> - Filter domains with discovered feeds</div>
|
||||
<div style="margin-bottom: 10px;"><span style="color: #0af;">f:hold f:pass f:skip f:fail</span> - Filter feeds by status</div>
|
||||
<div style="margin-bottom: 20px;"><span style="color: #0af;">[text]</span> - Search feeds</div>
|
||||
<div style="color: #666;">Click domain/feed name to drill down. Click status buttons to set status.</div>
|
||||
|
||||
+2
-1
@@ -512,6 +512,7 @@ const dashboardHTML = `<!DOCTYPE html>
|
||||
<button class="cmd-btn" data-cmd="domains:pass">d:pass</button>
|
||||
<button class="cmd-btn" data-cmd="domains:skip">d:skip</button>
|
||||
<button class="cmd-btn" data-cmd="domains:fail">d:fail</button>
|
||||
<button class="cmd-btn" data-cmd="domains:feeds">d:feeds</button>
|
||||
<span style="color: #333; margin: 0 4px;">|</span>
|
||||
<button class="cmd-btn" data-cmd="feeds:hold">f:hold</button>
|
||||
<button class="cmd-btn" data-cmd="feeds:pass">f:pass</button>
|
||||
@@ -533,7 +534,7 @@ const dashboardHTML = `<!DOCTYPE html>
|
||||
<div id="output"></div>
|
||||
</div>
|
||||
|
||||
<div style="color: #333; font-size: 11px; margin-top: 10px;">v26</div>
|
||||
<div style="color: #333; font-size: 11px; margin-top: 10px;">v35</div>
|
||||
|
||||
<div class="updated" id="updatedAt">Last updated: {{.UpdatedAt.Format "2006-01-02 15:04:05"}}</div>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user