diff --git a/entry/entry.go b/entry/entry.go index 2c2b5e5..12f2ccf 100644 --- a/entry/entry.go +++ b/entry/entry.go @@ -1,6 +1,9 @@ package entry import ( + "fmt" + "regexp" + "strings" "time" "code.chrissexton.org/cws/cabinet/db" @@ -22,10 +25,10 @@ type Entry struct { func PrepareTable(tx *sqlx.Tx) error { q := `create table if not exists entries ( id integer primary key, - slug text unique, - content text, - created datetime, - updated datetime, + slug text unique not null, + content text not null, + created datetime not null, + updated datetime not null, author_id integer )` _, err := tx.Exec(q) @@ -35,9 +38,10 @@ func PrepareTable(tx *sqlx.Tx) error { } q = `create table if not exists tags ( id integer primary key, - name text, + name text not null, entry_id integer, - foreign key(entry_id) references entries(id) + foreign key(entry_id) references entries(id), + constraint unique_name_id unique (name, entry_id) )` _, err = tx.Exec(q) if err != nil { @@ -74,12 +78,20 @@ func GetByID(db *db.Database, id int64) (Entry, error) { return e, e.populateTags() } -func GetAll(db *db.Database) ([]*Entry, error) { +func Search(db *db.Database, query string) ([]*Entry, error) { entries := []*Entry{} - q := `select * from entries` - err := db.Select(&entries, q) - if err != nil { - return nil, err + if query != "" { + q := `select * from entries where content like '%?%'` + err := db.Select(&entries, q, query) + if err != nil { + return nil, err + } + } else { + q := `select * from entries` + err := db.Select(&entries, q) + if err != nil { + return nil, err + } } for _, e := range entries { e.db = db @@ -117,13 +129,91 @@ func (e *Entry) populateTags() error { return err } +func (e *Entry) addTag(tag string) error { + q := `insert into tags (name,entry_id) values (?,?)` + _, err := e.db.Exec(q, tag, e.ID) + return err +} + +func (e *Entry) removeTag(tag string) error { + q := `delete from tags where name=? and entry_id=?` + _, err := e.db.Exec(q, tag, e.ID) + return err +} + +func (e *Entry) UniqueSlug() string { + candidate := strings.Split(e.Content, "\n")[0] + candidateNumber := 0 + + r := regexp.MustCompile(`[^a-zA-Z0-9 -]`) + candidate = r.ReplaceAllString(candidate, "") + candidate = strings.TrimSpace(candidate) + candidate = strings.ReplaceAll(candidate, " ", "-") + if len(candidate) == 0 { + candidate = "untitled" + } + candidate = strings.ToLower(candidate) + + q := `select slug from entries where slug like ?` + slugs := []string{} + if err := e.db.Select(&slugs, q, candidate+"%"); err != nil { + log.Debug().Err(err).Msgf("Could not get candidate slugs: %s", err) + return candidate + } + + contains := func(s string, ss []string) bool { + for _, e := range ss { + if s == e { + return true + } + } + return false + } + + tmpCandidate := candidate + for contains(tmpCandidate, slugs) { + candidateNumber++ + tmpCandidate = fmt.Sprintf("%s-%d", candidate, candidateNumber) + } + + return tmpCandidate +} + func (e *Entry) Update() error { if e.ID == -1 { return e.Create() } + old, err := GetByID(e.db, e.ID) + if err != nil { + return err + } + + e.Slug = e.UniqueSlug() + q := `update entries set slug=?, content=?, updated=?, author_id=? where id=?` - _, err := e.db.Exec(q, e.Slug, e.Content, e.Updated.Unix(), e.AuthorID, e.ID) - return err + if _, err = e.db.Exec(q, e.Slug, e.Content, e.Updated.Unix(), e.AuthorID, e.ID); err != nil { + return err + } + for _, t := range e.Tags { + if !contains(old.Tags, t) { + e.addTag(t) + } + } + for _, t := range old.Tags { + if !contains(e.Tags, t) { + e.removeTag(t) + } + } + return nil +} + +func contains(items []string, entry string) bool { + for _, e := range items { + if e == entry { + return true + } + } + return false } func (e *Entry) Create() error { diff --git a/frontend/package.json b/frontend/package.json index 9687dd4..c1386ee 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,6 +8,8 @@ "lint": "vue-cli-service lint" }, "dependencies": { + "asciidoctor": "^2.0.3", + "axios": "^0.19.0", "bootstrap": "^4.3.1", "bootstrap-vue": "^2.0.4", "brace": "latest", diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 79fd02d..0335ceb 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,7 +1,7 @@ \ No newline at end of file diff --git a/frontend/src/components/Viewer.vue b/frontend/src/components/Viewer.vue index 58b6205..80a8b93 100644 --- a/frontend/src/components/Viewer.vue +++ b/frontend/src/components/Viewer.vue @@ -1,16 +1,26 @@ diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index a719680..e37d8cf 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -6,11 +6,6 @@ import Console from '../views/Console.vue' Vue.use(VueRouter) const routes = [ - { - path: '/', - name: 'home', - component: Home - }, { path: '/view/:slug', name: 'home-slug', @@ -43,6 +38,11 @@ const routes = [ // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') + }, + { + path: '/', + name: 'home', + component: Home } ] diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js index 4d50a0d..6831687 100644 --- a/frontend/src/store/index.js +++ b/frontend/src/store/index.js @@ -1,25 +1,29 @@ import Vue from 'vue' import Vuex from 'vuex' -import _ from 'lodash' +// import _ from 'lodash' +import axios from 'axios' Vue.use(Vuex) -function getRandomInt(min, max) { - min = Math.ceil(min) - max = Math.floor(max) - return Math.floor(Math.random() * (max - min + 1)) + min -} +// function getRandomInt(min, max) { +// min = Math.ceil(min) +// max = Math.floor(max) +// return Math.floor(Math.random() * (max - min + 1)) + min +// } -var files = { - 'test-1': {id: 0, title: 'test 1', contents: '= test 1', slug: 'test-1', tags: ['a']}, - 'test-2': {id: 1, title: 'test 2', contents: '= test 2', slug: 'test-2', tags: ['b']}, - 'test-3': {id: 2, title: 'test 3', contents: '= test 3', slug: 'test-3', tags: ['a','b']}, - 'test-4': {id: 3, title: 'test 4', contents: '= test 4', slug: 'test-4', tags: ['c']} -} +// var files = { +// 'test-1': {id: 0, title: 'test 1', contents: '= test 1', slug: 'test-1', tags: ['a']}, +// 'test-2': {id: 1, title: 'test 2', contents: '= test 2', slug: 'test-2', tags: ['b']}, +// 'test-3': {id: 2, title: 'test 3', contents: '= test 3', slug: 'test-3', tags: ['a','b']}, +// 'test-4': {id: 3, title: 'test 4', contents: '= test 4', slug: 'test-4', tags: ['c']} +// } export default new Vuex.Store({ state: { - errs: [] + errs: [], + searchResults: [], + query: null, + file: null }, mutations: { clearError(state) { @@ -27,25 +31,49 @@ export default new Vuex.Store({ }, addError(state, err) { state.errs.push(err) + }, + setResults(state, results) { + state.searchResults = results + }, + setQuery(state, query) { + state.query = query + }, + setFile(state, file) { + state.file = file } }, actions: { - getFile: function(_, slug) { - console.log('getFile('+slug+')') - return new Promise(function (resolve) { - setTimeout(() => resolve(files[slug]), getRandomInt(0, 1000)) - }) + getFile: function({ commit }, slug) { + if (slug) + return axios.get('/v1/entries/'+slug) + .catch(err => commit('addError', err)) + .then(res => { + commit('setFile', res.data) + }) }, - getSearchResults: function (state, query) { - console.log('getSearchResults: '+query) - let values = Object.values(files) - if (query) - values = _.filter(values, (o) => { - return o.title.indexOf(query) != -1 + getSearchResults: function ({dispatch, commit}, query) { + commit('setQuery', query) + dispatch('updateSearch') + }, + updateSearch: function ({commit, state}) { + let query = state.query + if (query) { + axios.get('/v1/entries?query='+query) + .catch(err => state.addError(err)) + .then(res =>{ + console.log("getSearchResults:"+res.data) + commit('setResults', res.data) + }) + } + axios.get('/v1/entries') + .catch(err => state.addError(err)) + .then(res =>{ + console.log("getSearchResults:"+res.data) + commit('setResults', res.data) }) - return new Promise(function (resolve) { - setTimeout(() => resolve(values, getRandomInt(0, 1000))) - }) + }, + saveFile: function(state, file) { + return axios.put('/v1/entries/'+file.Slug, file) } }, modules: { diff --git a/frontend/src/views/Console.vue b/frontend/src/views/Console.vue index 3836c1c..ce3cebd 100644 --- a/frontend/src/views/Console.vue +++ b/frontend/src/views/Console.vue @@ -1,22 +1,31 @@