9 Commits
Author SHA1 Message Date
dominicf 6c780e5b8f Update /c/ banner 2026-08-24 13:05:49 -04:00
dominicf 1055495e1d Update index page 2026-08-24 12:12:03 -04:00
dominicf 28c643943f Styling/color enhancements 2026-08-24 11:33:19 -04:00
dominicf e2605dcd54 Styles for table and global links 2026-08-24 11:13:38 -04:00
dominicf 6dad00d41d Fix hot reload proxy address 2026-08-24 10:48:29 -04:00
dominicf fa37b2c302 Run and apply go fix 2026-08-24 10:40:23 -04:00
dominicf 9026b43c83 Update admin 2026-08-23 18:19:22 -04:00
dominicf 58b51e6e36 Fix err not propogating in GetAdmin 2026-08-23 18:09:11 -04:00
dominicf 16678c4e7a Patch XSS vuln 2026-08-23 18:06:29 -04:00
10 changed files with 107 additions and 64 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ internal/database/comfychan.db
tmp
out
media/
/media/
# direnv
.direnv
+1 -1
View File
@@ -1,5 +1,5 @@
live/templ:
templ generate --watch --proxy="http://localhost:8080" --cmd="go run ./web" --open-browser=false -v
templ generate --watch --proxy="http://localhost:7676" --cmd="go run ./web" --open-browser=false -v
live/sync_assets:
go run github.com/air-verse/air@latest \
+20 -20
View File
@@ -20,7 +20,7 @@ type Queryer interface {
func GetBoards(db *sql.DB) ([]Board, error) {
rows, err := db.Query(`
SELECT id, name, slug, tag
SELECT id, name, slug, tag
FROM boards ORDER BY slug`)
if err != nil {
@@ -43,8 +43,8 @@ func GetBoards(db *sql.DB) ([]Board, error) {
func GetBoard(db *sql.DB, slug string) (Board, error) {
row := db.QueryRow(`
SELECT id, name, slug, tag
FROM boards
SELECT id, name, slug, tag
FROM boards
WHERE slug = ?`, slug)
var result Board
@@ -58,8 +58,8 @@ func GetBoard(db *sql.DB, slug string) (Board, error) {
func GetThreads(db *sql.DB, boardSlug string) ([]Thread, error) {
rows, err := db.Query(`
SELECT id, board_slug, subject, created_at, bumped_at, pinned, locked
FROM threads
SELECT id, board_slug, subject, created_at, bumped_at, pinned, locked
FROM threads
WHERE board_slug = ?`, boardSlug)
if err != nil {
@@ -84,8 +84,8 @@ func GetThreads(db *sql.DB, boardSlug string) ([]Thread, error) {
func GetThread(db *sql.DB, threadId int) (Thread, error) {
row := db.QueryRow(`
SELECT id, board_slug, subject, created_at, bumped_at, pinned, locked
FROM threads
SELECT id, board_slug, subject, created_at, bumped_at, pinned, locked
FROM threads
WHERE id = ?`, threadId)
var t Thread
@@ -212,9 +212,9 @@ func DeleteThread(db Queryer, threadId int) error {
func GetPosts(db *sql.DB, threadId int) ([]Post, error) {
rows, err := db.Query(`
SELECT id, thread_id, author, body, created_at, media_path,
ip_hash, number, thumb_path, banned
FROM posts
SELECT id, thread_id, author, body, created_at, media_path,
ip_hash, number, thumb_path, banned
FROM posts
WHERE thread_id = ?`, threadId)
if err != nil {
@@ -239,10 +239,10 @@ func GetPosts(db *sql.DB, threadId int) ([]Post, error) {
func GetOriginalPost(db *sql.DB, threadId int) (Post, error) {
row := db.QueryRow(`
SELECT id, thread_id, author, body, created_at, media_path,
SELECT id, thread_id, author, body, created_at, media_path,
ip_hash, number, thumb_path, banned
FROM posts
WHERE thread_id = ?
FROM posts
WHERE thread_id = ?
ORDER BY created_at ASC LIMIT 1`, threadId)
var r Post
@@ -257,9 +257,9 @@ func GetOriginalPost(db *sql.DB, threadId int) (Post, error) {
func GetPost(db *sql.DB, postId int) (Post, error) {
row := db.QueryRow(`
SELECT id, thread_id, author, body, created_at, media_path,
SELECT id, thread_id, author, body, created_at, media_path,
ip_hash, number, thumb_path, banned
FROM posts
FROM posts
WHERE id = ?`, postId)
var r Post
@@ -275,7 +275,7 @@ func GetPost(db *sql.DB, postId int) (Post, error) {
func PutPost(db Queryer, boardSlug string, threadId int, body string, mediaPath string, thumbPath string, ip_hash string) error {
row := db.QueryRow(`
SELECT MAX(p.number)
FROM posts p
FROM posts p
INNER JOIN threads t ON p.thread_id = t.id
WHERE t.board_slug = ?`, boardSlug)
@@ -290,7 +290,7 @@ func PutPost(db Queryer, boardSlug string, threadId int, body string, mediaPath
}
_, err := db.Exec(`
INSERT INTO posts (thread_id, body, media_path, ip_hash, number, thumb_path)
INSERT INTO posts (thread_id, body, media_path, ip_hash, number, thumb_path)
VALUES (?, ?, ?, ?, ?, ?)`, threadId, body, mediaPath, ip_hash, newPostNumber, thumbPath)
if err != nil {
return err
@@ -337,7 +337,7 @@ func DeletePost(db *sql.DB, postId int) error {
// delete post
_, err := db.Exec(`
DELETE FROM posts
DELETE FROM posts
WHERE id = ?`, postId)
if err != nil {
return err
@@ -364,7 +364,7 @@ var ErrBanNotFound = errors.New("ban not found")
func GetBan(db *sql.DB, ip string) (Ban, error) {
row := db.QueryRow(`
SELECT ip_hash, reason, expiration
SELECT ip_hash, reason, expiration
FROM bans
where ip_hash = ?`, ip)
@@ -398,7 +398,7 @@ func GetAdmin(db *sql.DB, username string) (Admin, error) {
var result Admin
if err := row.Scan(&result.Username, &result.Password); err != nil {
return Admin{}, nil
return Admin{}, err
}
return result, nil
+6 -6
View File
@@ -11,8 +11,8 @@ CREATE TABLE IF NOT EXISTS boards (
tag TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS threads (
id INTEGER PRIMARY KEY AUTOINCREMENT,
CREATE TABLE IF NOT EXISTS threads (
id INTEGER PRIMARY KEY AUTOINCREMENT,
board_slug TEXT NOT NULL,
subject TEXT NOT NULL DEFAULT '',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
@@ -46,7 +46,7 @@ CREATE TABLE IF NOT EXISTS bans (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip_hash TEXT NOT NULL UNIQUE,
reason TEXT NOT NULL,
expiration DATETIME NOT NULL
expiration DATETIME NOT NULL
);
-- ======================
@@ -55,10 +55,10 @@ CREATE TABLE IF NOT EXISTS bans (
-- Boards
INSERT INTO boards (slug, name, tag) VALUES
('c', 'Comfy', 'Be comfy, fren'),
INSERT INTO boards (slug, name, tag) VALUES
('c', 'Comfy', 'Stay awhile'),
('r', 'Robots', 'Beep, boop'),
('gn', 'Goon', 'God is watching');
INSERT INTO admins (username, password) VALUES
('admin', '$2a$10$vRP4/9O6SwyUziEUtBLQM.r9C2WujIIZ6yEgqGjhlBaFPvtpfdHPC');
('admin', '$2a$10$hzmcLK2ZrEz0NTxr7eVuV.gn8shW.tQxD0D0vYUgAwADZly3U/BZ.');
+9 -13
View File
@@ -49,27 +49,23 @@ var urlRx = regexp.MustCompile(`(?i)\bhttps?://[^\s<]+`)
func EnrichPost(body string) string {
var b strings.Builder
for _, rawLine := range strings.Split(body, "\n") {
line := urlRx.ReplaceAllStringFunc(rawLine, func(u string) string {
esc := template.HTMLEscapeString(u)
return fmt.Sprintf(
`<a href="%[1]s" target="_blank" rel="noopener noreferrer" class="ext-link">%[1]s</a>`,
esc,
)
})
for rawLine := range strings.SplitSeq(body, "\n") {
var outLine string
for i, rawWord := range strings.Split(line, " ") {
for i, rawWord := range strings.Split(rawLine, " ") {
var outWord string
if strings.HasPrefix(rawWord, ">>") {
postId := strings.TrimPrefix(rawWord, ">>")
if postId, ok := strings.CutPrefix(rawWord, ">>"); ok {
outWord = fmt.Sprintf(
`<a onclick="onReplyLinkClick(event)" onmouseover="highlightPost(%[1]s,event)" `+
`onmouseleave="highlightPost(%[1]s,event,false)" href="#post-%[1]s" class="reply-link">%[2]s</a>`,
postId, template.HTMLEscapeString(rawWord),
)
} else if urlRx.MatchString(rawWord) {
outWord = fmt.Sprintf(
`<a href="%[1]s" target="_blank" rel="noopener noreferrer" class="ext-link">%[1]s</a>`,
template.HTMLEscapeString(rawWord),
)
} else {
outWord = rawWord
outWord = template.HTMLEscapeString(rawWord)
}
if i != 0 {
outLine += " "
+44 -21
View File
@@ -2,27 +2,28 @@
:root {
--bg-main: #1d1f21;
--text-color: #c5c8c6;
--text-muted: #81a2be;
--brand-red: #c5c8c6;
--text-muted: #85a28a;
--brand-green: #4e5f3f;
--brand-yellow: #db9f12;
--form-header-bg: #282a2e;
--border-light: #282a2e;
--subject: #b294bb;
--link-secondary: #81a2be;
--subject: #718a5b;
--link-secondary: #85a28a;
--post-bg: #282a2e;
--post-highlight: #D6BAD0;
--post-author: #c5c8c6;
--greentext: #b5bd68;
--greentext: #949055;
--reply-link: #81a2be;
--reply-link: #85a28a;
--dialog-bg: #EDEFF7;
--danger: #b294bb;
--warning-bg: #b294bb;
--danger: #4e5f3f;
--warning-bg: #929661;
--black: #000000;
--heading-bg: #b294bb45;
--heading-bg: #4e5f3f;
--heading-text: #FFFFFF;
--box-bg: #282a2e;
@@ -41,6 +42,15 @@ body {
color: var(--text-color)
}
a {
cursor: pointer;
color: var(--text-muted);
}
a:hover {
color: var(--danger);
}
/* INDEX */
#clavis {
@@ -52,25 +62,16 @@ body {
/* BOARD LIST */
#boardList {
font-size: 12pt;
cursor: default;
font-size: 14pt;
margin: 10px;
}
#boardList a {
cursor: pointer;
color: var(--text-muted);
}
#boardList a:hover {
color: var(--danger);
}
/* BOARD */
.board-header {
margin: 25px auto;
text-align: center;
color: var(--brand-red);
}
.board-header h1 {
@@ -78,15 +79,19 @@ body {
font-size: 1.8rem;
margin: 8px;
margin-top: 12px;
color: var(--brand-yellow);
}
.board-header p {
font-size: .8rem;
margin: 4px;
font-style: italic;
color: var(--brand-yellow);
}
.board-header img {
width: 300px;
height: 100px;
}
.action-bar {
@@ -422,11 +427,29 @@ body {
background-color: var(--box-bg);
}
.box h2 {
.box-table {
font-size: 10pt;
border: 1px solid var(--black);
background-color: var(--box-bg);
width: 100%;
text-align: center;
}
.box-table td {
padding: 5px;
}
.box-table tbody tr:nth-child(even) {
background-color: color-mix(in srgb, var(--box-bg) 85%, black);
}
.box h2,
.box-table th {
padding: 5px;
margin: 0;
background-color: var(--heading-bg);
color: var(--heading-text);
font-weight: bold;
}
.box p {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+26 -2
View File
@@ -4,16 +4,40 @@ import "github.com/dominicf2001/comfychan/web/views/shared"
templ Index() {
@shared.Layout("Comfychan") {
<img style="width: 150px; float: right;" src="/static/media/comfychan.png"/>
<section class="container">
<img id="clavis" src="/static/media/clavis.png"/>
<div class="box">
<h2>Welcome to Comfychan</h2>
<div>
<p>
<strong>Comfychan</strong> is place to share comfy images and videos.
<strong>Comfychan</strong> is place to share comfy images, videos and retardation.
</p>
</div>
</div>
</section>
<section style="margin-top: 25px;" class="container">
<table class="box-table">
<thead>
<tr>
<th>Board</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="/c">/c/ - Comfy</a></td>
<td>Stay awhile</td>
</tr>
<tr>
<td><a href="/r">/r/ - Robots</a></td>
<td>Beep, boop</td>
</tr>
<tr>
<td><a href="/gn">/gn/ - Goon</a></td>
<td>God is watching</td>
</tr>
</tbody>
</table>
</section>
}
}