add end read date
This commit is contained in:
@@ -4,17 +4,19 @@
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
icon: String,
|
icon: String,
|
||||||
legend: String,
|
legend: String,
|
||||||
isSet: Boolean
|
isSet: Boolean,
|
||||||
|
isReadonly: Boolean
|
||||||
});
|
});
|
||||||
|
|
||||||
const hovered = ref(false)
|
const hovered = ref(false)
|
||||||
|
|
||||||
const computedIcon = computed(() => props.icon + (hovered.value || props.isSet ? "Fill" : ""));
|
const computedIcon = computed(() => props.icon + (!props.isReadonly && (hovered.value || props.isSet) ? "Fill" : ""));
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bigiconandlegend"
|
<div class="bigiconandlegend"
|
||||||
|
:class="props.isReadonly ? '' : 'showcanclick'"
|
||||||
@mouseover="hovered = true"
|
@mouseover="hovered = true"
|
||||||
@mouseout="hovered = false">
|
@mouseout="hovered = false">
|
||||||
<span class="bigicon" :title="props.legend">
|
<span class="bigicon" :title="props.legend">
|
||||||
@@ -31,7 +33,7 @@
|
|||||||
margin:25px;
|
margin:25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bigiconandlegend:hover {
|
.showcanclick:hover {
|
||||||
transform: scale(1.03);
|
transform: scale(1.03);
|
||||||
transition: ease-in-out 0.02s;
|
transition: ease-in-out 0.02s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
113
front/src/BookDateWidget.vue
Normal file
113
front/src/BookDateWidget.vue
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import BigIcon from './BigIcon.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
icon: String,
|
||||||
|
legend: String,
|
||||||
|
startReadDate: String,
|
||||||
|
endReadDate: String,
|
||||||
|
isExpanded: Boolean,
|
||||||
|
isReadonly: Boolean,
|
||||||
|
useEndDate: Boolean,
|
||||||
|
lastWidget: Boolean
|
||||||
|
});
|
||||||
|
defineEmits(['onIconClick', 'onStartDateChange', 'onEndDateChange'])
|
||||||
|
|
||||||
|
const today = new Date().toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
function computeParentClasses() {
|
||||||
|
let classNames = "bookdatewidget";
|
||||||
|
if (props.isExpanded) {
|
||||||
|
classNames += " has-text-dark has-background-text";
|
||||||
|
}
|
||||||
|
if (props.lastWidget) {
|
||||||
|
classNames += " border-radius-right-and-left";
|
||||||
|
} else {
|
||||||
|
classNames += " border-radius-right";
|
||||||
|
}
|
||||||
|
if (props.isReadonly) {
|
||||||
|
classNames += " widget-readonly"
|
||||||
|
}
|
||||||
|
return classNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="computeParentClasses()">
|
||||||
|
<BigIcon :icon="props.icon"
|
||||||
|
:is-readonly="props.isReadonly"
|
||||||
|
:legend="props.legend"
|
||||||
|
:isSet="props.isExpanded"
|
||||||
|
@click="props.isReadonly ? null : $emit('onIconClick') "/>
|
||||||
|
<div v-if="props.isExpanded" class="inputdate">
|
||||||
|
<div class="ontopofinput">
|
||||||
|
<label class="datelabel" for="startread">
|
||||||
|
{{$t('bookdatewidget.started')}}
|
||||||
|
</label>
|
||||||
|
<input class="datepicker has-background-dark has-text-light"
|
||||||
|
id="startread"
|
||||||
|
type="date"
|
||||||
|
@change="(e) => $emit('onStartDateChange', e.target.value)"
|
||||||
|
:value="props.startReadDate"
|
||||||
|
:max="today"/>
|
||||||
|
<div v-if="props.useEndDate">
|
||||||
|
<label class="datelabel" for="endread">
|
||||||
|
{{$t('bookdatewidget.finished')}}
|
||||||
|
</label>
|
||||||
|
<input class="datepicker has-background-dark has-text-light"
|
||||||
|
id="endread"
|
||||||
|
type="date"
|
||||||
|
@change="(e) => $emit('onEndDateChange', e.target.value)"
|
||||||
|
:value="props.endReadDate"
|
||||||
|
:max="today"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.inputdate {
|
||||||
|
display: flex;
|
||||||
|
justify-content:center;
|
||||||
|
align-items:center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.bookdatewidget {
|
||||||
|
display: flex;
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-radius-right {
|
||||||
|
border-radius: 0 30px 30px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-radius-right-and-left {
|
||||||
|
border-radius: 0 30px 30px 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ontopofinput {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datelabel {
|
||||||
|
display: flex;
|
||||||
|
justify-content:center;
|
||||||
|
align-items:center;
|
||||||
|
font-size: 26px;
|
||||||
|
border: none;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker {
|
||||||
|
font-size: 26px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-readonly {
|
||||||
|
opacity: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,15 +2,16 @@
|
|||||||
|
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { getBook, getImagePathOrDefault, putReadBook, putWantReadBook, putRateBook,
|
import { getBook, getImagePathOrDefault, putReadBook, putWantReadBook, putRateBook,
|
||||||
putStartReadDate, putStartReadDateUnset } from './api.js'
|
putStartReadDate, putStartReadDateUnset, putEndReadDate, putEndReadDateUnset, putUnreadBook } from './api.js'
|
||||||
import { onBeforeRouteUpdate } from 'vue-router'
|
import { onBeforeRouteUpdate } from 'vue-router'
|
||||||
import { VRating } from 'vuetify/components/VRating';
|
import { VRating } from 'vuetify/components/VRating';
|
||||||
import BigIcon from './BigIcon.vue';
|
import BigIcon from './BigIcon.vue';
|
||||||
import StartReadWidget from './StartReadWidget.vue';
|
import BookDateWidget from './BookDateWidget.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
id: String
|
id: String
|
||||||
});
|
});
|
||||||
|
const today = new Date().toISOString().slice(0, 10);
|
||||||
|
|
||||||
let data = ref(null);
|
let data = ref(null);
|
||||||
let error = ref(null);
|
let error = ref(null);
|
||||||
@@ -34,8 +35,11 @@
|
|||||||
data.value.read = !data.value.read;
|
data.value.read = !data.value.read;
|
||||||
if (data.value.read) {
|
if (data.value.read) {
|
||||||
data.value.wantread = false;
|
data.value.wantread = false;
|
||||||
|
data.value.endReadDate = today;
|
||||||
|
putEndReadDate(props.id, today);
|
||||||
|
} else {
|
||||||
|
putUnreadBook(props.id);
|
||||||
}
|
}
|
||||||
putReadBook(props.id, {read: data.value.read});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onWantReadIconClick() {
|
function onWantReadIconClick() {
|
||||||
@@ -45,7 +49,7 @@
|
|||||||
|
|
||||||
function onStartReadIconClick() {
|
function onStartReadIconClick() {
|
||||||
if (!data.value.startReadDate) {
|
if (!data.value.startReadDate) {
|
||||||
data.value.startReadDate = new Date().toISOString().slice(0, 10);
|
data.value.startReadDate = today;
|
||||||
putStartReadDate(props.id, data.value.startReadDate);
|
putStartReadDate(props.id, data.value.startReadDate);
|
||||||
} else {
|
} else {
|
||||||
data.value.startReadDate = null;
|
data.value.startReadDate = null;
|
||||||
@@ -55,7 +59,26 @@
|
|||||||
|
|
||||||
function onStartReadDateChange(d) {
|
function onStartReadDateChange(d) {
|
||||||
data.value.startReadDate = d;
|
data.value.startReadDate = d;
|
||||||
putStartReadDate(props.id, data.value.startReadDate);
|
if (d != "") {
|
||||||
|
putStartReadDate(props.id, data.value.startReadDate);
|
||||||
|
} else {
|
||||||
|
putStartReadDateUnset(props.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEndReadDateChange(d) {
|
||||||
|
data.value.endReadDate = d;
|
||||||
|
if (d != "") {
|
||||||
|
putEndReadDate(props.id, data.value.endReadDate);
|
||||||
|
} else {
|
||||||
|
putEndReadDateUnset(props.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStartReadExpanded() {
|
||||||
|
let isStartReadDateSet = data.value.startReadDate ? true : false;
|
||||||
|
let isReadUnset = !data.value.read ? true : false;
|
||||||
|
return isStartReadDateSet && isReadUnset;
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -85,20 +108,30 @@
|
|||||||
<p>{{data.summary}}</p>
|
<p>{{data.summary}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="iconscontainer">
|
<div class="iconscontainer" :class="data.read ? 'remove-border-bottom' : ''">
|
||||||
<BigIcon icon="BIconEye"
|
<BigIcon icon="BIconEye"
|
||||||
:legend="$t('bookform.wantread')"
|
:legend="$t('bookform.wantread')"
|
||||||
:isSet="data.wantread"
|
:isSet="data.wantread"
|
||||||
@click="onWantReadIconClick"/>
|
@click="onWantReadIconClick"/>
|
||||||
<StartReadWidget
|
<BookDateWidget
|
||||||
|
icon="BIconBook"
|
||||||
:legend="$t('bookform.startread')"
|
:legend="$t('bookform.startread')"
|
||||||
:startReadDate="data.startReadDate"
|
:start-read-date="data.startReadDate"
|
||||||
@onDateChange="onStartReadDateChange"
|
:is-expanded="isStartReadExpanded()"
|
||||||
|
:is-readonly="data.read"
|
||||||
|
@onStartDateChange="onStartReadDateChange"
|
||||||
@onIconClick="onStartReadIconClick"/>
|
@onIconClick="onStartReadIconClick"/>
|
||||||
<BigIcon icon="BIconCheckCircle"
|
<BookDateWidget
|
||||||
|
icon="BIconCheckCircle"
|
||||||
:legend="$t('bookform.read')"
|
:legend="$t('bookform.read')"
|
||||||
:isSet="data.read"
|
:start-read-date="data.startReadDate"
|
||||||
@click="onReadIconClick"/>
|
use-end-date
|
||||||
|
last-widget
|
||||||
|
:endReadDate="data.endReadDate"
|
||||||
|
:isExpanded="data.read"
|
||||||
|
@onStartDateChange="onStartReadDateChange"
|
||||||
|
@onEndDateChange="onEndReadDateChange"
|
||||||
|
@onIconClick="onReadIconClick"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -124,5 +157,9 @@ img {
|
|||||||
width: 250px;
|
width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.remove-border-bottom {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
const error = ref(null)
|
const error = ref(null)
|
||||||
|
|
||||||
async function onUserBookRead() {
|
async function onUserBookRead() {
|
||||||
const res = await putReadBook(props.id, {read: true});
|
const res = await putReadBook(props.id);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
router.push('/books')
|
router.push('/books')
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import BigIcon from './BigIcon.vue';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
legend: String,
|
|
||||||
startReadDate: String
|
|
||||||
});
|
|
||||||
defineEmits(['onIconClick', 'onDateChange'])
|
|
||||||
|
|
||||||
const today = new Date().toISOString().slice(0, 10);
|
|
||||||
|
|
||||||
function computeParentClasses() {
|
|
||||||
if (props.startReadDate) {
|
|
||||||
return "startdatewidget has-text-dark has-background-text"
|
|
||||||
} else {
|
|
||||||
return "startdatewidget"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :class="computeParentClasses()">
|
|
||||||
<BigIcon icon="BIconBook"
|
|
||||||
:legend="props.legend"
|
|
||||||
:isSet="props.startReadDate ? true : false"
|
|
||||||
@click="$emit('onIconClick')"/>
|
|
||||||
<div v-if="props.startReadDate" class="inputdate">
|
|
||||||
<div class="ontopofinput">
|
|
||||||
<label class="datelabel" for="startread">
|
|
||||||
{{$t('startreadwidget.started')}}
|
|
||||||
</label>
|
|
||||||
<input class="datepicker has-background-dark has-text-light"
|
|
||||||
id="startread"
|
|
||||||
type="date"
|
|
||||||
@change="(e) => $emit('onDateChange', e.target.value)"
|
|
||||||
:value="props.startReadDate"
|
|
||||||
:max="today"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
.inputdate {
|
|
||||||
display: flex;
|
|
||||||
justify-content:center;
|
|
||||||
align-items:center;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.startdatewidget {
|
|
||||||
display: flex;
|
|
||||||
width: 500px;
|
|
||||||
border-radius: 0 30px 30px 0;
|
|
||||||
}
|
|
||||||
.ontopofinput {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datelabel {
|
|
||||||
display: flex;
|
|
||||||
justify-content:center;
|
|
||||||
align-items:center;
|
|
||||||
font-size: 26px;
|
|
||||||
border: none;
|
|
||||||
padding-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datepicker {
|
|
||||||
font-size: 26px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -49,8 +49,20 @@ export function postBook(book) {
|
|||||||
return genericPayloadCall('/book', book.value, 'POST')
|
return genericPayloadCall('/book', book.value, 'POST')
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function putReadBook(bookId, payload) {
|
export async function putReadBook(bookId) {
|
||||||
return genericPayloadCall('/book/' + bookId + "/read", payload, 'PUT')
|
return genericPayloadCall('/book/' + bookId + "/read", {read: true}, 'PUT')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function putUnreadBook(bookId) {
|
||||||
|
return genericPayloadCall('/book/' + bookId + "/read", {read: false}, 'PUT')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function putEndReadDate(bookId, enddate) {
|
||||||
|
return genericPayloadCall('/book/' + bookId + "/read", {read: true, endDate: enddate}, 'PUT')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function putEndReadDateUnset(bookId) {
|
||||||
|
return genericPayloadCall('/book/' + bookId + "/read", {read: true, endDate: "null"}, 'PUT')
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function putStartReadDateUnset(bookId) {
|
export async function putStartReadDateUnset(bookId) {
|
||||||
|
|||||||
@@ -54,7 +54,8 @@
|
|||||||
"goto":"Goto page {pageNumber}",
|
"goto":"Goto page {pageNumber}",
|
||||||
"page":"Page {pageNumber}"
|
"page":"Page {pageNumber}"
|
||||||
},
|
},
|
||||||
"startreadwidget" :{
|
"bookdatewidget" :{
|
||||||
"started": "Started at :"
|
"started": "Started at :",
|
||||||
|
"finished": "Finished at :"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,8 @@
|
|||||||
"goto":"Aller à la page {pageNumber}",
|
"goto":"Aller à la page {pageNumber}",
|
||||||
"page":"Page {pageNumber}"
|
"page":"Page {pageNumber}"
|
||||||
},
|
},
|
||||||
"startreadwidget" :{
|
"bookdatewidget" :{
|
||||||
"started": "Commencé le :"
|
"started": "Commencé le :",
|
||||||
|
"finished": "Fini le :"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type fetchedBook struct {
|
|||||||
Read bool `json:"read"`
|
Read bool `json:"read"`
|
||||||
WantRead bool `json:"wantread"`
|
WantRead bool `json:"wantread"`
|
||||||
StartReadDate string `json:"startReadDate"`
|
StartReadDate string `json:"startReadDate"`
|
||||||
|
EndReadDate string `json:"endReadDate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetBook_Ok(t *testing.T) {
|
func TestGetBook_Ok(t *testing.T) {
|
||||||
|
|||||||
@@ -20,6 +20,44 @@ func TestPutReadUserBooks_NewReadOk(t *testing.T) {
|
|||||||
assert.Equal(t, false, book.WantRead)
|
assert.Equal(t, false, book.WantRead)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPutReadUserBooks_NewReadDateOk(t *testing.T) {
|
||||||
|
payload :=
|
||||||
|
`{
|
||||||
|
"read": true,
|
||||||
|
"endDate": "2025-10-20"
|
||||||
|
}`
|
||||||
|
bookId := "9"
|
||||||
|
testPutReadUserBooks(t, payload, bookId, http.StatusOK)
|
||||||
|
book := testGetBook(t, bookId, http.StatusOK)
|
||||||
|
assert.Equal(t, true, book.Read)
|
||||||
|
assert.Equal(t, "2025-10-20", book.EndReadDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPutReadUserBooks_UnsetEndDate(t *testing.T) {
|
||||||
|
payload :=
|
||||||
|
`{
|
||||||
|
"read": true,
|
||||||
|
"endDate": "null"
|
||||||
|
}`
|
||||||
|
bookId := "9"
|
||||||
|
testPutReadUserBooks(t, payload, bookId, http.StatusOK)
|
||||||
|
book := testGetBook(t, bookId, http.StatusOK)
|
||||||
|
assert.Equal(t, true, book.Read)
|
||||||
|
assert.Equal(t, "", book.EndReadDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPutReadUserBooks_UnsetReadOk(t *testing.T) {
|
||||||
|
payload :=
|
||||||
|
`{
|
||||||
|
"read": false
|
||||||
|
}`
|
||||||
|
bookId := "9"
|
||||||
|
testPutReadUserBooks(t, payload, bookId, http.StatusOK)
|
||||||
|
book := testGetBook(t, bookId, http.StatusOK)
|
||||||
|
assert.Equal(t, false, book.Read)
|
||||||
|
assert.Equal(t, "", book.EndReadDate)
|
||||||
|
}
|
||||||
|
|
||||||
func testPutReadUserBooks(t *testing.T, payload string, bookId string, expectedCode int) {
|
func testPutReadUserBooks(t *testing.T, payload string, bookId string, expectedCode int) {
|
||||||
testutils.TestBookPutCallWithDemoPayload(t, payload, bookId, expectedCode, "/book/"+bookId+"/read")
|
testutils.TestBookPutCallWithDemoPayload(t, payload, bookId, expectedCode, "/book/"+bookId+"/read")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,4 +16,5 @@ type UserBook struct {
|
|||||||
Read bool
|
Read bool
|
||||||
WantRead bool
|
WantRead bool
|
||||||
StartReadDate *time.Time
|
StartReadDate *time.Time
|
||||||
|
EndReadDate *time.Time
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,13 +16,19 @@ type BookGet struct {
|
|||||||
Read bool `json:"read"`
|
Read bool `json:"read"`
|
||||||
WantRead bool `json:"wantread"`
|
WantRead bool `json:"wantread"`
|
||||||
StartReadDate string `json:"startReadDate"`
|
StartReadDate string `json:"startReadDate"`
|
||||||
|
EndReadDate string `json:"endReadDate"`
|
||||||
CoverPath string `json:"coverPath"`
|
CoverPath string `json:"coverPath"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchBookGet(db *gorm.DB, userId uint, bookId uint64) (BookGet, error) {
|
func FetchBookGet(db *gorm.DB, userId uint, bookId uint64) (BookGet, error) {
|
||||||
var book BookGet
|
var book BookGet
|
||||||
query := db.Model(&model.Book{})
|
query := db.Model(&model.Book{})
|
||||||
query = query.Select("books.title, books.author, books.summary, user_books.rating, user_books.read, user_books.want_read, DATE(user_books.start_read_date) as start_read_date, " + selectStaticFilesPath())
|
selectQueryString := "books.title, books.author, books.summary, " +
|
||||||
|
"user_books.rating, user_books.read, user_books.want_read, " +
|
||||||
|
"DATE(user_books.start_read_date) as start_read_date, " +
|
||||||
|
"DATE(user_books.end_read_date) AS end_read_date, " +
|
||||||
|
selectStaticFilesPath()
|
||||||
|
query = query.Select(selectQueryString)
|
||||||
query = query.Joins("left join user_books on (user_books.book_id = books.id and user_books.user_id = ?)", userId)
|
query = query.Joins("left join user_books on (user_books.book_id = books.id and user_books.user_id = ?)", userId)
|
||||||
query = query.Joins("left join static_files on (static_files.id = books.cover_id)")
|
query = query.Joins("left join static_files on (static_files.id = books.cover_id)")
|
||||||
query = query.Where("books.id = ?", bookId)
|
query = query.Where("books.id = ?", bookId)
|
||||||
|
|||||||
@@ -33,11 +33,25 @@ func PutReadUserBookHandler(ac appcontext.AppContext) {
|
|||||||
|
|
||||||
userbook.Read = read.Read
|
userbook.Read = read.Read
|
||||||
|
|
||||||
|
if read.EndDate != "" {
|
||||||
|
d, err := parseDate(read.EndDate)
|
||||||
|
if err != nil {
|
||||||
|
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userbook.EndReadDate = d
|
||||||
|
}
|
||||||
|
|
||||||
//remove the book from "wanted" list when it is marked as read.
|
//remove the book from "wanted" list when it is marked as read.
|
||||||
if userbook.Read {
|
if userbook.Read {
|
||||||
userbook.WantRead = false
|
userbook.WantRead = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//clear the date when unread
|
||||||
|
if !userbook.Read {
|
||||||
|
userbook.EndReadDate = nil
|
||||||
|
}
|
||||||
|
|
||||||
ac.Db.Save(&userbook)
|
ac.Db.Save(&userbook)
|
||||||
ac.C.String(http.StatusOK, "Success")
|
ac.C.String(http.StatusOK, "Success")
|
||||||
}
|
}
|
||||||
@@ -84,17 +98,12 @@ func PutStartReadUserBookHandler(ac appcontext.AppContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//string equal to "null" to unset value
|
d, err := parseDate(startDateToParse.StartDate)
|
||||||
if startDateToParse.StartDate == "null" {
|
if err != nil {
|
||||||
userbook.StartReadDate = nil
|
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||||
} else {
|
return
|
||||||
startDate, err := time.Parse(time.DateOnly, startDateToParse.StartDate)
|
|
||||||
if err != nil {
|
|
||||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userbook.StartReadDate = &startDate
|
|
||||||
}
|
}
|
||||||
|
userbook.StartReadDate = d
|
||||||
|
|
||||||
ac.Db.Save(&userbook)
|
ac.Db.Save(&userbook)
|
||||||
ac.C.String(http.StatusOK, "Success")
|
ac.C.String(http.StatusOK, "Success")
|
||||||
@@ -130,7 +139,8 @@ func PutRateUserBookHandler(ac appcontext.AppContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type userbookPutRead struct {
|
type userbookPutRead struct {
|
||||||
Read bool `json:"read"`
|
Read bool `json:"read"`
|
||||||
|
EndDate string `json:"endDate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type userbookPutWantRead struct {
|
type userbookPutWantRead struct {
|
||||||
@@ -150,6 +160,16 @@ type apiCallData struct {
|
|||||||
User model.User
|
User model.User
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseDate(dateToParse string) (*time.Time, error) {
|
||||||
|
//string equal to "null" to unset value
|
||||||
|
if dateToParse == "null" {
|
||||||
|
return nil, nil
|
||||||
|
} else {
|
||||||
|
startDate, err := time.Parse(time.DateOnly, dateToParse)
|
||||||
|
return &startDate, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func retrieveDataFromContext(ac appcontext.AppContext) (apiCallData, error) {
|
func retrieveDataFromContext(ac appcontext.AppContext) (apiCallData, error) {
|
||||||
bookId64, err := strconv.ParseUint(ac.C.Param("id"), 10, 64)
|
bookId64, err := strconv.ParseUint(ac.C.Param("id"), 10, 64)
|
||||||
bookId := uint(bookId64)
|
bookId := uint(bookId64)
|
||||||
|
|||||||
Reference in New Issue
Block a user