kennel #13
24
api/serve.go
24
api/serve.go
|
@ -172,10 +172,28 @@ func MakeServer(argv *args.Arguments, dbConn *sql.DB) *http.Server {
|
|||
LogRequestContinuation(requestContext, r, w)(auth.VerifySessionContinuation, FailurePassingContinuation)(hcaptcha.CaptchaVerificationContinuation, hcaptcha.CaptchaVerificationContinuation)(guestbook.SignGuestbookContinuation, FailurePassingContinuation)(guestbook.ListGuestbookContinuation, guestbook.ListGuestbookContinuation)(hcaptcha.CaptchaArgsContinuation, hcaptcha.CaptchaArgsContinuation)(template.TemplateContinuation("guestbook.html", true), template.TemplateContinuation("guestbook.html", true))(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
|
||||
})
|
||||
|
||||
mux.HandleFunc("GET /{name}", func(w http.ResponseWriter, r *http.Request) {
|
||||
/*
|
||||
mux.HandleFunc("GET /kennel", func(w http.ResponseWriter, r *http.Request) {
|
||||
requestContext := makeRequestContext()
|
||||
LogRequestContinuation(requestContext, r, w)(kennel.GetKennelStateContinuation, kennel.GetKennelStateContinuation)(template.TemplateContinuation("kennel.json", true), template.TemplateContinuation("kennel.json", true))(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
|
||||
})
|
||||
|
||||
mux.HandleFunc("GET /kennel/cats", func(w http.ResponseWriter, r *http.Request) {
|
||||
requestContext := makeRequestContext()
|
||||
LogRequestContinuation(requestContext, r, w)(auth.VerifySessionContinuation, FailurePassingContinuation)(kennel.ListUserCats, auth.GoLoginContinuation)(template.TemplateContinuation("kennel_cats.html", true), FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
|
||||
})
|
||||
|
||||
|
||||
mux.HandleFunc("POST /kennel", func(w http.ResponseWriter, r *http.Request) {
|
||||
requestContext := makeRequestContext()
|
||||
LogRequestContinuation(requestContext, r, w)(auth.VerifySessionContinuation, FailurePassingContinuation)(hcaptcha.CaptchaVerificationContinuation, hcaptcha.CaptchaVerificationContinuation)(guestbook.SignGuestbookContinuation, FailurePassingContinuation)(guestbook.ListGuestbookContinuation, guestbook.ListGuestbookContinuation)(hcaptcha.CaptchaArgsContinuation, hcaptcha.CaptchaArgsContinuation)(template.TemplateContinuation("guestbook.html", true), template.TemplateContinuation("guestbook.html", true))(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
|
||||
})
|
||||
*/
|
||||
|
||||
mux.HandleFunc("GET /{template}", func(w http.ResponseWriter, r *http.Request) {
|
||||
requestContext := makeRequestContext()
|
||||
name := r.PathValue("name")
|
||||
LogRequestContinuation(requestContext, r, w)(auth.VerifySessionContinuation, FailurePassingContinuation)(IdContinuation, IdContinuation)(template.TemplateContinuation(name+".html", true), FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
|
||||
templateFile := r.PathValue("template")
|
||||
LogRequestContinuation(requestContext, r, w)(auth.VerifySessionContinuation, FailurePassingContinuation)(IdContinuation, IdContinuation)(template.TemplateContinuation(templateFile+".html", true), FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
|
||||
})
|
||||
|
||||
return &http.Server{
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
type KennelCat struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Link string `json:"link"`
|
||||
Description string `json:"description"`
|
||||
Spritesheet string `json:"spritesheet"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type KennelState struct {
|
||||
At time.Time `json:"at"`
|
||||
EncodedState string `json:"state"`
|
||||
}
|
||||
|
||||
func CountUserKennelCats(db *sql.DB, userID string) (int, error) {
|
||||
log.Println("counting kennel cats for user", userID)
|
||||
|
||||
row := db.QueryRow("SELECT COUNT(*) FROM kennel_cat WHERE user_id = ?", userID)
|
||||
var count int
|
||||
err := row.Scan(&count)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func GetUserKennelCats(db *sql.DB, userID string) ([]KennelCat, error) {
|
||||
log.Println("getting kennel cats for user", userID)
|
||||
|
||||
rows, err := db.Query("SELECT * FROM kennel_cat WHERE user_id = ?", userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var cats []KennelCat
|
||||
for rows.Next() {
|
||||
var cat KennelCat
|
||||
err := rows.Scan(&cat.ID, &cat.UserID, &cat.Link, &cat.Description, &cat.Spritesheet, &cat.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cats = append(cats, cat)
|
||||
}
|
||||
|
||||
return cats, nil
|
||||
}
|
||||
|
||||
func SaveKennelCat(db *sql.DB, cat *KennelCat) (*KennelCat, error) {
|
||||
log.Println("saving kennel cat", cat.ID)
|
||||
|
||||
if (cat.CreatedAt == time.Time{}) {
|
||||
cat.CreatedAt = time.Now()
|
||||
}
|
||||
|
||||
_, err := db.Exec("INSERT OR REPLACE INTO kennel_cat (id, user_id, link, description, spritesheet, created_at) VALUES (?, ?, ?, ?, ?, ?)", cat.ID, cat.UserID, cat.Link, cat.Description, cat.Spritesheet, cat.CreatedAt)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cat, nil
|
||||
}
|
||||
|
||||
func GetKennelCat(db *sql.DB, catID string) (*KennelCat, error) {
|
||||
log.Println("getting kennel cat", catID)
|
||||
|
||||
row := db.QueryRow("SELECT * FROM kennel_cat WHERE id = ?", catID)
|
||||
var cat KennelCat
|
||||
err := row.Scan(&cat.ID, &cat.UserID, &cat.Link, &cat.Description, &cat.Spritesheet, &cat.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cat, nil
|
||||
}
|
||||
|
||||
func DeleteKennelCat(db *sql.DB, catID string) error {
|
||||
log.Println("deleting kennel cat", catID)
|
||||
|
||||
_, err := db.Exec("DELETE FROM kennel_cat WHERE id = ?", catID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetKennel(dbConn *sql.DB) ([]KennelCat, error) {
|
||||
log.Println("getting kennel")
|
||||
|
||||
rows, err := dbConn.Query("SELECT * FROM kennel_cat")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var cats []KennelCat
|
||||
for rows.Next() {
|
||||
var cat KennelCat
|
||||
err := rows.Scan(&cat.ID, &cat.UserID, &cat.Link, &cat.Description, &cat.Spritesheet, &cat.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cats = append(cats, cat)
|
||||
}
|
||||
|
||||
return cats, nil
|
||||
}
|
||||
|
||||
func GetKennelState(dbConn *sql.DB) (*KennelState, error) {
|
||||
log.Println("getting kennel state")
|
||||
|
||||
row := dbConn.QueryRow("SELECT * FROM kennel_state")
|
||||
var state KennelState
|
||||
err := row.Scan(&state.At, &state.EncodedState)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &state, nil
|
||||
}
|
||||
|
||||
func SaveKennelState(dbConn *sql.DB, state *KennelState) (*KennelState, error) {
|
||||
log.Println("saving kennel state")
|
||||
|
||||
_, err := dbConn.Exec("INSERT OR REPLACE INTO kennel_state (at, state) VALUES (?, ?)", state.At, state.EncodedState)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return state, nil
|
||||
}
|
|
@ -162,6 +162,33 @@ func MigrateProfiles(dbConn *sql.DB) (*sql.DB, error) {
|
|||
return dbConn, nil
|
||||
}
|
||||
|
||||
func MigrateKennel(dbConn *sql.DB) (*sql.DB, error) {
|
||||
log.Println("migrating kennel tables")
|
||||
|
||||
_, err := dbConn.Exec(`CREATE TABLE IF NOT EXISTS kennel_cat (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
link TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
spritesheet TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
);`)
|
||||
if err != nil {
|
||||
return dbConn, err
|
||||
}
|
||||
|
||||
_, err = dbConn.Exec(`CREATE TABLE IF NOT EXISTS kennel_state (
|
||||
at LONG INTEGER PRIMARY KEY,
|
||||
encoded_state TEXT NOT NULL
|
||||
);`)
|
||||
if err != nil {
|
||||
return dbConn, err
|
||||
}
|
||||
|
||||
return dbConn, nil
|
||||
}
|
||||
|
||||
func Migrate(dbConn *sql.DB) (*sql.DB, error) {
|
||||
log.Println("migrating database")
|
||||
|
||||
|
@ -173,6 +200,7 @@ func Migrate(dbConn *sql.DB) (*sql.DB, error) {
|
|||
MigrateDNSRecords,
|
||||
MigrateGuestBook,
|
||||
MigrateProfiles,
|
||||
MigrateKennel,
|
||||
}
|
||||
|
||||
for _, migration := range migrations {
|
||||
|
|
|
@ -18,32 +18,6 @@
|
|||
font-family: "ComicSans", sans-serif;
|
||||
|
||||
cursor: url("/static/img/cursor-1.png"), auto;
|
||||
-webkit-animation: cursor 400ms infinite;
|
||||
animation: cursor 400ms infinite;
|
||||
}
|
||||
|
||||
@-webkit-keyframes cursor {
|
||||
0% {
|
||||
cursor: url("/static/img/cursor-2.png"), auto;
|
||||
}
|
||||
50% {
|
||||
cursor: url("/static/img/cursor-1.png"), auto;
|
||||
}
|
||||
100% {
|
||||
cursor: url("/static/img/cursor-2.png"), auto;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes cursor {
|
||||
0% {
|
||||
cursor: url("/static/img/cursor-2.png"), auto;
|
||||
}
|
||||
50% {
|
||||
cursor: url("/static/img/cursor-1.png"), auto;
|
||||
}
|
||||
100% {
|
||||
cursor: url("/static/img/cursor-2.png"), auto;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
|
|
|
@ -2,18 +2,16 @@
|
|||
|
||||
<p>hatecomputers.club is a club for those that, well, <i>hate computers!</i></p>
|
||||
<br>
|
||||
<p>we maintain a little homelab mesh for our own little computing community! as a member you'll have access to split-zone dns for our intranet as well as ownership to "*.USER.hatecomputers.club." for which you can do almost anything. we host, and plan to host, all kinds of stuff for each other :).</p>
|
||||
<p>we maintain a little homelab mesh for our own little computing community! as a member you'll have access to split-zone dns for our intranet as well as ownership to "*.USER.hatecomputers.club." for which you can do almost anything. we host, and plan to host, all kinds of stuff for each other :)</p>
|
||||
<br>
|
||||
<p>you may align with us if you believe any (or all) of the following:</p>
|
||||
<br>
|
||||
<ul>
|
||||
<li>computers, software, and technological advancements need to serve public interests first and foremost, and not private capital.</li>
|
||||
<li>computers and the internet in the hands of capitalism have caused potentially irreversible effects on our society and the wellbeing of humans.</li>
|
||||
<li>computers and the integration of automation in most human jobs should be implemented with heavy distrust of the systems developed and account for the well-being of any affected persons. especially when pertaining to "generative technologies".</li>
|
||||
<li>computers and the integration of automation in most human jobs should be implemented with heavy distrust of the systems developed and account for the well-being of any affected persons. this applies in weight to "generative technologies".</li>
|
||||
<li>computers and the data associated to, or describing of, a human or their activities should be protected with utmost security.</li>
|
||||
<li>the security and maintenance responsiblities of computers and operation thereof, when having direct implications on an individual's livelihood, or the public good, (banking, work, payments, etc.), should fall on the groups building such systems and not on the individual users of such systems. i.e. a person must be sufficiently repaid in full should ownership of digital goods be stolen.</li>
|
||||
<li>in addition, the acts of tampering, modification, and "hacking" a computer, as long as said actions are served in good intentions and the benefit of the public's best interests and not private capital, should not be punishable and instead praised or rewarded in relation to the scope of the information, methodology, impact, and results obtained.</li>
|
||||
<li>computers and systems should not have been corporatized and should have been, and continue to be, completely open to the user and researchers to develop upon.</li>
|
||||
<li>the acts of tampering, modification, and "hacking" a computer, as long as said actions are served with good intention and the benefit of the public and not private capital, should not be punishable; instead, praised or rewarded in relation to the scope of the information, methodology, impact, and results obtained.</li>
|
||||
<li>computers should not have been corporatized and should have been, and continue to be, completely open to the user and researchers to develop upon.</li>
|
||||
</ul>
|
||||
|
||||
{{ end }}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
{{ define "content" }}
|
||||
<table>
|
||||
<tr>
|
||||
<th>type.</th>
|
||||
<th>name.</th>
|
||||
<th>content.</th>
|
||||
<th>ttl.</th>
|
||||
<th>internal.</th>
|
||||
<th>created.</th>
|
||||
<th>delete.</th>
|
||||
</tr>
|
||||
{{ if (eq (len .DNSRecords) 0) }}
|
||||
<tr>
|
||||
<td colspan="7"><span class="blinky">no dns records found.</span></td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ range $record := .DNSRecords }}
|
||||
<tr>
|
||||
<td>{{ $record.Type }}</td>
|
||||
<td>{{ $record.Name }}</td>
|
||||
<td>{{ $record.Content }}</td>
|
||||
<td>{{ $record.TTL }}</td>
|
||||
<td>{{ $record.Internal }}</td>
|
||||
<td class="time">{{ $record.CreatedAt }}</td>
|
||||
<td>
|
||||
<form method="POST" action="/dns/delete">
|
||||
<input type="hidden" name="id" value="{{ $record.ID }}" />
|
||||
<input type="submit" value="Delete" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</table>
|
||||
<br>
|
||||
<form method="POST" action="/dns" class="form">
|
||||
<h2>add dns records.</h2>
|
||||
<p>note that the name <em>must</em> be a subdomain of <em>{{ .User.Username }}</em></p>
|
||||
<hr>
|
||||
<label for="type">type.</label>
|
||||
<input type="text" name="type" placeholder="CNAME"
|
||||
{{ if not .RecordForm }}
|
||||
placeholder="CNAME"
|
||||
{{ else }}
|
||||
value="{{ .RecordForm.Type }}"
|
||||
{{ end }}
|
||||
required />
|
||||
<label for="name">name.</label>
|
||||
<input type="text" name="name"
|
||||
{{ if not .RecordForm }}
|
||||
placeholder="{{ .User.Username }} || endpoint.{{ .User.Username }}..."
|
||||
{{ else }}
|
||||
value="{{ .RecordForm.Name }}"
|
||||
{{ end }}
|
||||
required/>
|
||||
<label for="content">content.</label>
|
||||
<input type="text" name="content"
|
||||
{{ if not .RecordForm }}
|
||||
placeholder="{{ .User.Username }}.dev"
|
||||
{{ else }}
|
||||
value="{{ .RecordForm.Content }}"
|
||||
{{ end }}
|
||||
required />
|
||||
<label for="ttl">ttl.</label>
|
||||
<input type="text" name="ttl"
|
||||
{{ if not .RecordForm }}
|
||||
placeholder="43200"
|
||||
{{ else }}
|
||||
value="{{ .RecordForm.TTL }}"
|
||||
{{ end }}
|
||||
required />
|
||||
<label for="internal">
|
||||
internal.
|
||||
<input style='display:inline;width:auto;' type="checkbox" name="internal"
|
||||
{{ if .RecordForm.Internal }}
|
||||
checked
|
||||
{{ end }}
|
||||
/>
|
||||
</label>
|
||||
<input type="submit" value="add." />
|
||||
</form>
|
||||
{{ end }}
|
Loading…
Reference in New Issue