Compare commits
10 Commits
98d9d8a899
...
ce02dca041
Author | SHA1 | Date |
---|---|---|
Chris Sexton | ce02dca041 | |
Chris Sexton | b3eda2c3cf | |
cws | 2fdf9ce7e6 | |
Chris Sexton | c43f8c18ca | |
Chris Sexton | def6e6f24f | |
Chris Sexton | e80b138f67 | |
Chris Sexton | 6b350ef201 | |
Chris Sexton | 3c39830c4d | |
Chris Sexton | 0d32edbe0e | |
Chris Sexton | 723f628c31 |
59
auth/auth.go
59
auth/auth.go
|
@ -1,9 +1,17 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.chrissexton.org/cws/cabinet/db"
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
"code.chrissexton.org/cws/cabinet/config"
|
||||||
|
"code.chrissexton.org/cws/cabinet/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
|
@ -12,21 +20,44 @@ type User struct {
|
||||||
ID int64
|
ID int64
|
||||||
Name string
|
Name string
|
||||||
Hash []byte
|
Hash []byte
|
||||||
|
AuthKey string `db:"auth_key"`
|
||||||
|
Invalidate time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func PrepareTable(tx *sqlx.Tx) error {
|
func PrepareTable(tx *sqlx.Tx) error {
|
||||||
q := `create table if not exists users (
|
q := `create table if not exists users (
|
||||||
id integer primary key,
|
id integer primary key,
|
||||||
name text unique,
|
name text unique not null,
|
||||||
hash text
|
hash text not null,
|
||||||
|
auth_key text,
|
||||||
|
invalidate datetime
|
||||||
)`
|
)`
|
||||||
_, err := tx.Exec(q)
|
_, err := tx.Exec(q)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeKey() (string, error) {
|
||||||
|
keySize := config.GetInt("key.size", 10)
|
||||||
|
buf := make([]byte, keySize)
|
||||||
|
_, err := rand.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
key := hex.EncodeToString(buf)
|
||||||
|
log.Debug().Msgf("Encoded secret key %s as %s", string(buf), key)
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
func New(db *db.Database, name, password string) (*User, error) {
|
func New(db *db.Database, name, password string) (*User, error) {
|
||||||
q := `insert into users (null, ?, ?)`
|
q := `insert into users values (null, ?, ?, ?, ?)`
|
||||||
res, err := db.Exec(q, name, password)
|
|
||||||
|
key, err := makeKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
invalidate := time.Now().Add(time.Duration(config.GetInt("invalidate.hours", 7*24)) * time.Hour)
|
||||||
|
|
||||||
|
res, err := db.Exec(q, name, password, key, invalidate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -37,6 +68,8 @@ func New(db *db.Database, name, password string) (*User, error) {
|
||||||
u := &User{
|
u := &User{
|
||||||
ID: id,
|
ID: id,
|
||||||
Name: name,
|
Name: name,
|
||||||
|
AuthKey: key,
|
||||||
|
Invalidate: invalidate,
|
||||||
}
|
}
|
||||||
u.Set(password)
|
u.Set(password)
|
||||||
return u, nil
|
return u, nil
|
||||||
|
@ -67,3 +100,19 @@ func (u *User) Validate(password string) bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetByKey(db *db.Database, key string) (*User, error) {
|
||||||
|
q := `select * from users where auth_key = ?`
|
||||||
|
u := &User{}
|
||||||
|
invalid := errors.New("invalid key")
|
||||||
|
if err := db.Get(u, q, key); err != nil {
|
||||||
|
if err.Error() == "sql: no rows in result set" {
|
||||||
|
return nil, invalid
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if u.Invalidate.Before(time.Now()) {
|
||||||
|
return nil, invalid
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"code.chrissexton.org/cws/cabinet/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMakeKey(t *testing.T) {
|
||||||
|
k, err := makeKey()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotEmpty(t, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetByKey(t *testing.T) {
|
||||||
|
d, err := db.New(":memory:")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
tx, err := d.Beginx()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
err = PrepareTable(tx)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
err = tx.Commit()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u, err := New(d, "test", "abc")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u2, err := GetByKey(d, u.AuthKey)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, u.ID, u2.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetByKeyFailure(t *testing.T) {
|
||||||
|
d, err := db.New(":memory:")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
tx, err := d.Beginx()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
err = PrepareTable(tx)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
err = tx.Commit()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
_, err = New(d, "test", "abc")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u2, err := GetByKey(d, "foobar")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, u2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
d, err := db.New(":memory:")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
tx, err := d.Beginx()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
err = PrepareTable(tx)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
err = tx.Commit()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u, err := New(d, "test", "abc")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u2, err := Get(d, "test")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, u.ID, u2.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUser_Validate(t *testing.T) {
|
||||||
|
d, err := db.New(":memory:")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
tx, err := d.Beginx()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
err = PrepareTable(tx)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
err = tx.Commit()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
u, err := New(d, "test", "abc")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
actual := u.Validate("abc")
|
||||||
|
assert.True(t, actual)
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetInt(key string, fallback int) int {
|
||||||
|
v := Get(key, strconv.Itoa(fallback))
|
||||||
|
if out, err := strconv.Atoi(v); err == nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(key, fallback string) string {
|
||||||
|
key = strings.ToUpper(key)
|
||||||
|
key = strings.ReplaceAll(key, ".", "_")
|
||||||
|
if v, found := os.LookupEnv(key); found {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
2
db/db.go
2
db/db.go
|
@ -2,6 +2,8 @@ package db
|
||||||
|
|
||||||
import "github.com/jmoiron/sqlx"
|
import "github.com/jmoiron/sqlx"
|
||||||
|
|
||||||
|
import _ "github.com/mattn/go-sqlite3"
|
||||||
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
*sqlx.DB
|
*sqlx.DB
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
FROM alpine:edge
|
||||||
|
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
RUN apk add --no-cache musl-dev
|
||||||
|
RUN apk add --no-cache gcc
|
||||||
|
RUN apk add --no-cache sqlite
|
||||||
|
RUN apk add --no-cache go
|
||||||
|
RUN apk add --no-cache make
|
||||||
|
RUN apk add --no-cache npm
|
||||||
|
RUN apk add --no-cache yarn
|
||||||
|
|
||||||
|
VOLUME /app/var
|
||||||
|
EXPOSE 5673
|
||||||
|
|
||||||
|
ARG gomaxprocs="8"
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV SRC_DIR=/app/src/
|
||||||
|
|
||||||
|
ENV GOMAXPROCS=${gomaxprocs}
|
||||||
|
|
||||||
|
RUN git clone https://code.chrissexton.org/cws/cabinet.git $SRC_DIR
|
||||||
|
|
||||||
|
RUN apk add --no-cache tzdata
|
||||||
|
ENV TZ America/New_York
|
||||||
|
|
||||||
|
# RUN yarn global add @vue/cli
|
||||||
|
RUN cd $SRC_DIR/frontend; yarn && yarn build
|
||||||
|
RUN go get -u github.com/gobuffalo/packr/v2/packr2
|
||||||
|
RUN cd $SRC_DIR; $HOME/go/bin/packr2
|
||||||
|
RUN cd $SRC_DIR; go get ./...; go build -o /app/cabinet
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/cabinet", "-httpAddr=0.0.0.0:5673", "-db=/app/var/cabinet.db"]
|
|
@ -52,6 +52,14 @@ func PrepareTable(tx *sqlx.Tx) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewFromMd(db *db.Database, body string) *Entry {
|
||||||
|
e := New(db)
|
||||||
|
e.Content = body
|
||||||
|
e.Title = e.GenerateTitle()
|
||||||
|
e.Slug = e.UniqueSlug()
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
func New(db *db.Database) *Entry {
|
func New(db *db.Database) *Entry {
|
||||||
e := Entry{
|
e := Entry{
|
||||||
db: db,
|
db: db,
|
||||||
|
|
|
@ -11,8 +11,11 @@
|
||||||
"@vue/cli": "^4.0.5",
|
"@vue/cli": "^4.0.5",
|
||||||
"asciidoctor": "^2.0.3",
|
"asciidoctor": "^2.0.3",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
|
"jquery": "^1.9.1",
|
||||||
|
"popper.js": "^1.14.7",
|
||||||
"bootstrap": "^4.3.1",
|
"bootstrap": "^4.3.1",
|
||||||
"bootstrap-vue": "^2.0.4",
|
"bootstrap-vue": "^2.0.4",
|
||||||
|
"bootswatch": "^4.3.1",
|
||||||
"brace": "latest",
|
"brace": "latest",
|
||||||
"core-js": "^3.3.2",
|
"core-js": "^3.3.2",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
|
@ -30,6 +33,9 @@
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^5.16.0",
|
||||||
"eslint-plugin-vue": "^5.0.0",
|
"eslint-plugin-vue": "^5.0.0",
|
||||||
|
"sass": "^1.23.0",
|
||||||
|
"sass-loader": "^8.0.0",
|
||||||
|
"webpack": "^4.36.0",
|
||||||
"vue-template-compiler": "^2.6.10"
|
"vue-template-compiler": "^2.6.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
<title>frontend</title>
|
<title>Cabinet</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<b-navbar type="dark" variant="dark">
|
<b-navbar type="dark" variant="primary" class="navbar">
|
||||||
<b-navbar-brand>🗄 Cabinet</b-navbar-brand>
|
<b-navbar-brand>🗄 Cabinet</b-navbar-brand>
|
||||||
<b-navbar-nav>
|
<b-navbar-nav>
|
||||||
<b-nav-item to="/">Home</b-nav-item>
|
<b-nav-item to="/">Home</b-nav-item>
|
||||||
|
@ -18,26 +18,11 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style lang="scss">
|
||||||
#app {
|
.navbar {
|
||||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
padding: 0.5em !important;
|
||||||
-webkit-font-smoothing: antialiased;
|
margin-bottom: 1em;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
}
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav {
|
|
||||||
padding: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav a {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav a.router-link-exact-active {
|
|
||||||
color: #42b983;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<editor ref="myEditor" v-model="text" @init="editorInit" lang="asciidoc" theme="github" width="100%" height="500" />
|
<editor
|
||||||
|
ref="myEditor"
|
||||||
|
v-model="text"
|
||||||
|
@init="editorInit"
|
||||||
|
lang="asciidoc"
|
||||||
|
theme="tomorrow_night"
|
||||||
|
width="100%"
|
||||||
|
height="500" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -32,7 +39,7 @@
|
||||||
editorInit: function () {
|
editorInit: function () {
|
||||||
require('brace/ext/language_tools') //language extension prerequsite...
|
require('brace/ext/language_tools') //language extension prerequsite...
|
||||||
require('brace/mode/asciidoc') //language
|
require('brace/mode/asciidoc') //language
|
||||||
require('brace/theme/github')
|
require('brace/theme/tomorrow_night')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.$emit('markDirty', false)
|
this.$emit('markDirty', false)
|
||||||
this.$store.dispatch('updateSearch')
|
this.$store.dispatch('updateSearch')
|
||||||
if (res.data.Slug != this.$route.params.slug)
|
if (res.data.Slug !== this.$route.params.slug)
|
||||||
this.$router.replace({params: { slug: res.data.Slug }})
|
this.$router.replace({params: { slug: res.data.Slug }})
|
||||||
})
|
})
|
||||||
.catch(() => { })
|
.catch(() => { })
|
||||||
|
|
|
@ -1,22 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<div :hidden="!content">
|
<div :hidden="!content">
|
||||||
<b-container fluid>
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<Viewer :content="content" />
|
<Viewer :content="content" />
|
||||||
</b-col>
|
<b-card-group :hidden="!tags">
|
||||||
</b-row>
|
<b-card
|
||||||
<b-row>
|
style="max-width: 50%"
|
||||||
<b-col cols="10">
|
header="Tags"
|
||||||
<label for="tagList" :hidden="!tags">Tags</label>
|
header-tag="header"
|
||||||
</b-col>
|
>
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col cols="10">
|
|
||||||
<TagList id="tagList" :tags="tags" :readOnly="true" />
|
<TagList id="tagList" :tags="tags" :readOnly="true" />
|
||||||
</b-col>
|
</b-card>
|
||||||
</b-row>
|
</b-card-group>
|
||||||
</b-container>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<b-container fluid>
|
<b-container fluid>
|
||||||
<b-row>
|
<b-row>
|
||||||
|
<b-col class="searchBox">
|
||||||
<b-input placeholder="Search" @update="getResults" v-model="queryText" />
|
<b-input placeholder="Search" @update="getResults" v-model="queryText" />
|
||||||
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
<b-row v-for="item in results" v-bind:key="item.ID">
|
<b-row v-for="item in results" v-bind:key="item.ID">
|
||||||
<b-col>
|
<b-col>
|
||||||
<b-button :hidden="!editMode" size="sm" class="deleteLink" @click="deleteFile(item.Slug)">X</b-button> <b-link
|
<b-button :hidden="!editMode" size="sm" class="deleteLink" @click="deleteFile(item.Slug)">X</b-button>
|
||||||
|
<b-link
|
||||||
|
class="searchLink"
|
||||||
:to="{ name: target, params: { slug: item.Slug } }"
|
:to="{ name: target, params: { slug: item.Slug } }"
|
||||||
>{{item.Title}}</b-link>
|
>{{item.Title}}</b-link>
|
||||||
</b-col>
|
</b-col>
|
||||||
|
@ -59,4 +63,11 @@
|
||||||
.deleteLink {
|
.deleteLink {
|
||||||
font-size: x-small;
|
font-size: x-small;
|
||||||
}
|
}
|
||||||
|
.searchBox {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
.searchLink {
|
||||||
|
margin-left: 1em;
|
||||||
|
color: var(--info);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -5,7 +5,11 @@ import store from './store'
|
||||||
|
|
||||||
import BootstrapVue from 'bootstrap-vue'
|
import BootstrapVue from 'bootstrap-vue'
|
||||||
|
|
||||||
import 'bootstrap/dist/css/bootstrap.css'
|
// import "bootswatch/dist/darkly/variables";
|
||||||
|
// import "bootstrap/scss/bootstrap";
|
||||||
|
import "bootswatch/dist/darkly/bootstrap.css";
|
||||||
|
|
||||||
|
// import 'bootstrap/dist/css/bootstrap.css'
|
||||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
|
@ -2,9 +2,18 @@
|
||||||
<b-container fluid>
|
<b-container fluid>
|
||||||
<b-row>
|
<b-row>
|
||||||
<b-col md="5">
|
<b-col md="5">
|
||||||
<h2>Scratchpad</h2>
|
<div>
|
||||||
|
<b-tabs content-class="mt-3">
|
||||||
|
<b-tab active>
|
||||||
|
<template v-slot:title>
|
||||||
|
Scratchpad
|
||||||
|
</template>
|
||||||
<ScratchPad />
|
<ScratchPad />
|
||||||
|
</b-tab>
|
||||||
|
</b-tabs>
|
||||||
|
</div>
|
||||||
</b-col>
|
</b-col>
|
||||||
|
|
||||||
<b-col md="5">
|
<b-col md="5">
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -24,8 +33,16 @@
|
||||||
|
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col md="2">
|
<b-col md="2">
|
||||||
<h2>Search Results</h2>
|
<div>
|
||||||
|
<b-tabs content-class="mt-3">
|
||||||
|
<b-tab active>
|
||||||
|
<template v-slot:title>
|
||||||
|
Search
|
||||||
|
</template>
|
||||||
<SearchResults :editMode="true" target="console-slug" />
|
<SearchResults :editMode="true" target="console-slug" />
|
||||||
|
</b-tab>
|
||||||
|
</b-tabs>
|
||||||
|
</div>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
</b-container>
|
</b-container>
|
||||||
|
|
4538
frontend/yarn.lock
4538
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
1
go.mod
1
go.mod
|
@ -11,6 +11,7 @@ require (
|
||||||
github.com/rs/zerolog v1.16.0
|
github.com/rs/zerolog v1.16.0
|
||||||
github.com/speps/go-hashids v2.0.0+incompatible
|
github.com/speps/go-hashids v2.0.0+incompatible
|
||||||
github.com/stretchr/graceful v1.2.15
|
github.com/stretchr/graceful v1.2.15
|
||||||
|
github.com/stretchr/testify v1.4.0
|
||||||
golang.org/x/crypto v0.0.0-20191107222254-f4817d981bb6
|
golang.org/x/crypto v0.0.0-20191107222254-f4817d981bb6
|
||||||
golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c // indirect
|
golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c // indirect
|
||||||
google.golang.org/appengine v1.6.5 // indirect
|
google.golang.org/appengine v1.6.5 // indirect
|
||||||
|
|
20
todo.adoc
20
todo.adoc
|
@ -1,20 +0,0 @@
|
||||||
= Todo
|
|
||||||
:icons: font
|
|
||||||
|
|
||||||
* Backend
|
|
||||||
** Authentication
|
|
||||||
*** [ ] some kind of user auth
|
|
||||||
** save endpoint
|
|
||||||
*** [ ] add authentication/authorization
|
|
||||||
*** [ ] convert document to adoc (give format?)
|
|
||||||
*** [ ] check for unique tags
|
|
||||||
** [ ] search endpoint
|
|
||||||
*** [ ] search for tags
|
|
||||||
*** [ ] fulltext search
|
|
||||||
**** with link:https://blevesearch.com/docs/Getting%20Started/[Bleve]
|
|
||||||
* [ ] CLI Frontend
|
|
||||||
* [ ] Operations
|
|
||||||
** [ ] dockerize the build
|
|
||||||
** [ ] integrate CI/CD
|
|
||||||
** [ ] run on https://cabinet.chrissexton.org[cabinet.chrissexton.org]
|
|
||||||
** [ ] create redirect or https://cab.chrissexton.org[cab.chrissexton.org]
|
|
35
web/entry.go
35
web/entry.go
|
@ -3,6 +3,7 @@ package web
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -16,13 +17,16 @@ func (web *Web) editEntry(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
slug := vars["slug"]
|
slug := vars["slug"]
|
||||||
dec := json.NewDecoder(r.Body)
|
dec := json.NewDecoder(r.Body)
|
||||||
newEntry := entry.New(web.db)
|
req := struct {
|
||||||
err := dec.Decode(&newEntry)
|
Content string
|
||||||
|
}{}
|
||||||
|
err := dec.Decode(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
fmt.Fprint(w, err)
|
fmt.Fprint(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
newEntry := entry.NewFromMd(web.db, req.Content)
|
||||||
|
|
||||||
oldEntry, err := entry.GetBySlug(web.db, slug)
|
oldEntry, err := entry.GetBySlug(web.db, slug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -32,6 +36,8 @@ func (web *Web) editEntry(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
oldEntry.Content = newEntry.Content
|
oldEntry.Content = newEntry.Content
|
||||||
|
oldEntry.Title = newEntry.Title
|
||||||
|
oldEntry.Slug = newEntry.UniqueSlug()
|
||||||
oldEntry.Tags = newEntry.Tags
|
oldEntry.Tags = newEntry.Tags
|
||||||
oldEntry.Updated = time.Now()
|
oldEntry.Updated = time.Now()
|
||||||
|
|
||||||
|
@ -48,8 +54,31 @@ func (web *Web) editEntry(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Interface("oldEntry", oldEntry).Msg("Got a PUT")
|
w.Header().Set("content-type", "application/json")
|
||||||
|
fmt.Fprint(w, string(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (web *Web) newMarkdownEntry(w http.ResponseWriter, r *http.Request) {
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newEntry := entry.NewFromMd(web.db, string(body))
|
||||||
|
err = newEntry.Create()
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := json.Marshal(newEntry)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
w.Header().Set("content-type", "application/json")
|
w.Header().Set("content-type", "application/json")
|
||||||
fmt.Fprint(w, string(resp))
|
fmt.Fprint(w, string(resp))
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,13 +70,13 @@ func (web *Web) routeSetup() http.Handler {
|
||||||
// curl 'http://127.0.0.1:8080/v1/test' -X POST -H 'Accept: application/json, text/plain, */*' --compressed -H 'Content-Type: application/json;charset=utf-8' --data '{ "test": 1 }'
|
// curl 'http://127.0.0.1:8080/v1/test' -X POST -H 'Accept: application/json, text/plain, */*' --compressed -H 'Content-Type: application/json;charset=utf-8' --data '{ "test": 1 }'
|
||||||
|
|
||||||
api.HandleFunc("/entries", web.allEntries).Methods(http.MethodGet)
|
api.HandleFunc("/entries", web.allEntries).Methods(http.MethodGet)
|
||||||
authedApi.HandleFunc("/entries", web.newEntry).Methods(http.MethodPost)
|
api.HandleFunc("/entries", web.newEntry).Methods(http.MethodPost)
|
||||||
authedApi.HandleFunc("/entries", web.newEntry).Methods(http.MethodPost).
|
api.HandleFunc("/entries", web.newEntry).Methods(http.MethodPost).
|
||||||
HeadersRegexp("Content-Type", "application/(text|json).*")
|
HeadersRegexp("Content-Type", "application/(text|json).*")
|
||||||
//authedApi.HandleFunc("/entries", web.newMarkdownEntry).Methods(http.MethodPost).
|
api.HandleFunc("/entries", web.newMarkdownEntry).Methods(http.MethodPost).
|
||||||
// HeadersRegexp("Content-Type", "application/markdown.*")
|
HeadersRegexp("Content-Type", "application/markdown.*")
|
||||||
authedApi.HandleFunc("/entries/{slug}", web.removeEntry).Methods(http.MethodDelete)
|
api.HandleFunc("/entries/{slug}", web.removeEntry).Methods(http.MethodDelete)
|
||||||
authedApi.HandleFunc("/entries/{slug}", web.editEntry).Methods(http.MethodPut)
|
api.HandleFunc("/entries/{slug}", web.editEntry).Methods(http.MethodPut)
|
||||||
|
|
||||||
api.HandleFunc("/entries/{slug}", web.getEntry).Methods(http.MethodGet)
|
api.HandleFunc("/entries/{slug}", web.getEntry).Methods(http.MethodGet)
|
||||||
api.HandleFunc("/auth", web.auth).Methods(http.MethodPost)
|
api.HandleFunc("/auth", web.auth).Methods(http.MethodPost)
|
||||||
|
|
Loading…
Reference in New Issue