Add import edition from inventaire

This commit is contained in:
2026-02-01 21:49:11 +01:00
parent 27a9faf704
commit 464405f5c9
13 changed files with 266 additions and 162 deletions

View File

@@ -1,16 +1,17 @@
<script setup> <script setup>
import { computed } from 'vue' import { ref, computed } from 'vue'
import { getInventaireImagePathOrDefault } from './api.js' import { getInventaireImagePathOrDefault, postImportBook } from './api.js'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const router = useRouter(); const router = useRouter();
const props = defineProps({ const props = defineProps({
id: String, uri: String,
title: String, title: String,
image: String, image: String,
publisher: String, publisher: String,
date: String, date: String,
isbn: String, isbn: String,
lang: String
}); });
function displayDate(date) { function displayDate(date) {
@@ -22,21 +23,38 @@
return date; return date;
} }
} }
const error = ref(null);
const data = ref(null);
const importing = ref(false);
async function importInventaireEdition() {
importing.value = true;
const res = await postImportBook(props.uri, navigator.language.substring(0,2));
const json = await res.json();
if (res.ok) {
router.push(`/book/${json.id}`);
} else {
error.value = json;
}
}
const imagePathOrDefault = computed(() => getInventaireImagePathOrDefault(props.image)); const imagePathOrDefault = computed(() => getInventaireImagePathOrDefault(props.image));
</script> </script>
<template> <template>
<div v-if="error" class="notification is-danger">
<p>{{error}}</p>
</div>
<div class="columns no-padding box container has-background-dark"> <div class="columns no-padding box container has-background-dark">
<div class="media column no-margin clickable" @click="openBook"> <div v-if="importing && !data && !error">{{$t('importlistelement.importing')}}</div>
<div v-else class="media column no-margin clickable" @click="importInventaireEdition">
<div class="media-left"> <div class="media-left">
<figure class="image mb-3"> <figure class="image mb-3">
<img v-bind:src="imagePathOrDefault" v-bind:alt="title"> <img v-bind:src="imagePathOrDefault" v-bind:alt="title">
</figure> </figure>
</div> </div>
<div class="media-content"> <div class="media-content">
<div v-if="error" class="has-text-danger">
<p>{{error}}</p>
</div>
<div v-else>
<div class="is-size-4">{{title}}</div> <div class="is-size-4">{{title}}</div>
<div v-if="props.date" class="is-size-5">{{$t('importlistelement.releasedate') + "" + <div v-if="props.date" class="is-size-5">{{$t('importlistelement.releasedate') + "" +
displayDate(date)}}</div> displayDate(date)}}</div>
@@ -45,6 +63,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<style scoped> <style scoped>

View File

@@ -1,32 +0,0 @@
<script setup>
import { ref } from 'vue'
import { postImportBook } from './api.js'
import { useRouter } from 'vue-router'
const router = useRouter();
const props = defineProps({
inventaireid: String
});
const error = ref(null);
const data = ref(null);
async function importInventaireId() {
const res = await postImportBook(props.inventaireid, navigator.language.substring(0,2));
const json = await res.json();
if (res.ok) {
router.push(`/book/${json.id}`);
} else {
error.value = json;
}
}
importInventaireId();
</script>
<template>
<div v-if="error">{{error}}</div>
<div v-else-if="!data">Importing {{props.inventaireid}}...</div>
</template>
<style scoped></style>

View File

@@ -70,7 +70,8 @@
}, },
"importlistelement": { "importlistelement": {
"releasedate":"Release date:", "releasedate":"Release date:",
"publisher":"Publisher:" "publisher":"Publisher:",
"importing":"Importing..."
} }
} }

View File

@@ -70,6 +70,7 @@
}, },
"importlistelement": { "importlistelement": {
"releasedate":"Date de publication : ", "releasedate":"Date de publication : ",
"publisher":"Maison d'édition : " "publisher":"Maison d'édition : ",
"importing":"Import en cours..."
} }
} }

View File

@@ -9,14 +9,12 @@ import LogIn from './LogIn.vue'
import Home from './Home.vue' import Home from './Home.vue'
import SearchBook from './SearchBook.vue' import SearchBook from './SearchBook.vue'
import ImportInventaire from './ImportInventaire.vue' import ImportInventaire from './ImportInventaire.vue'
import InventaireImport from './InventaireImport.vue'
import { useAuthStore } from './auth.store' import { useAuthStore } from './auth.store'
const routes = [ const routes = [
{ path: '/', component: Home }, { path: '/', component: Home },
{ path: '/books', component: BooksBrowser }, { path: '/books', component: BooksBrowser },
{ path: '/book/:id', component: BookForm, props: true }, { path: '/book/:id', component: BookForm, props: true },
{ path: '/importinventaire/:inventaireid', component: InventaireImport, props: true },
{ path: '/author/:id', component: AuthorForm, props: true }, { path: '/author/:id', component: AuthorForm, props: true },
{ path: '/search/:searchterm', component: SearchBook, props: true }, { path: '/search/:searchterm', component: SearchBook, props: true },
{ path: '/import/inventaire/:inventaireid', component: ImportInventaire, props: true }, { path: '/import/inventaire/:inventaireid', component: ImportInventaire, props: true },

View File

@@ -18,19 +18,19 @@ type hasId struct {
} }
func TestPostImportBookHandler_Ok(t *testing.T) { func TestPostImportBookHandler_Ok(t *testing.T) {
id := testPostImportBookHandler(t, "Q202975", http.StatusOK) id := testPostImportBookHandler(t, "isbn:9782253004752", http.StatusOK)
book := testGetBook(t, strconv.FormatUint(uint64(id), 10), 200) book := testGetBook(t, strconv.FormatUint(uint64(id), 10), 200)
assert.Equal(t, "Les Hauts de Hurlevent", book.Title) assert.Equal(t, "les Hauts de Hurle-Vent", book.Title)
assert.Equal(t, "Emily Brontë", book.Author) assert.Equal(t, "Emily Brontë", book.Author)
assert.Equal(t, "Q202975", book.InventaireID) assert.Equal(t, "isbn:9782253004752", book.InventaireID)
} }
func TestPostImportBookHandler_OkAuthorKey(t *testing.T) { func TestPostImportBookHandler_OkAuthorKey(t *testing.T) {
id := testPostImportBookHandler(t, "Q1317915", http.StatusOK) id := testPostImportBookHandler(t, "isbn:9782290033630", http.StatusOK)
book := testGetBook(t, strconv.FormatUint(uint64(id), 10), 200) book := testGetBook(t, strconv.FormatUint(uint64(id), 10), 200)
assert.Equal(t, "Dr Bloodmoney", book.Title) assert.Equal(t, "Dr Bloodmoney", book.Title)
assert.Equal(t, "Philip K. Dick", book.Author) assert.Equal(t, "Philip K. Dick", book.Author)
assert.Equal(t, "Q1317915", book.InventaireID) assert.Equal(t, "isbn:9782290033630", book.InventaireID)
} }
func TestPostImportBookHandler_NoOLID(t *testing.T) { func TestPostImportBookHandler_NoOLID(t *testing.T) {
@@ -61,7 +61,10 @@ func testPostImportBookHandler(t *testing.T, openlibraryid string, expectedCode
t.Error(err) t.Error(err)
} }
return parsedId.ID return parsedId.ID
} else { } else if expectedCode == 200 {
var stringerr = w.Body.String()
t.Error(stringerr)
return 0 return 0
} }
return 0
} }

View File

@@ -43,7 +43,7 @@ func TestCallInventaireSearch_Offset(t *testing.T) {
} }
func TestCallInventaireBook_BraveNewWorld(t *testing.T) { func TestCallInventaireBook_BraveNewWorld(t *testing.T) {
result, err := CallInventaireBook("Q191949", "fr") result, err := callInventaireBook("wd:Q191949", "fr")
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@@ -54,8 +54,8 @@ func TestCallInventaireBook_BraveNewWorld(t *testing.T) {
assert.Equal(t, "écrivain, romancier et philosophe britannique (18941963)", result.Author.Description) assert.Equal(t, "écrivain, romancier et philosophe britannique (18941963)", result.Author.Description)
} }
func TestCallInventaireEdition_TestLimit(t *testing.T) { func TestCallInventaireEditionFromWork_TestLimit(t *testing.T) {
result, err := CallInventaireEdition("wd:Q339761", "fr", 10, 0) result, err := CallInventaireEditionFromWork("wd:Q339761", "fr", 10, 0)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@@ -63,8 +63,8 @@ func TestCallInventaireEdition_TestLimit(t *testing.T) {
assert.Equal(t, 10, len(result.Results)) assert.Equal(t, 10, len(result.Results))
} }
func TestCallInventaireEdition_TestOffset(t *testing.T) { func TestCallInventaireEditionFromWork_TestOffset(t *testing.T) {
result, err := CallInventaireEdition("wd:Q3213142", "fr", 0, 0) result, err := CallInventaireEditionFromWork("wd:Q3213142", "fr", 0, 0)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@@ -101,3 +101,27 @@ func TestCallInventaireEdition_TestOffset(t *testing.T) {
}, },
result.Results[2]) result.Results[2])
} }
func TestCallInventaireEdition(t *testing.T) {
result, err := CallInventaireEdition("isbn:9782266003698", "fr")
if err != nil {
t.Error(err)
}
assert.Equal(t,
InventaireEditionDetailedSingleResult{
Id: "isbn:9782266003698",
Title: "Bel-Ami",
Author: &InventaireAuthorResult{
ID: "Q9327",
Name: "Guy de Maupassant",
Description: "écrivain et journaliste littéraire français (1850-1893)",
},
Description: "roman de Guy De Maupassant",
ISBN: "978-2-266-00369-8",
Publisher: "Pocket",
ReleaseDate: "1977",
Image: "https://inventaire.io/img/entities/b237c0b5de5f6c765928d9eee26b55804a33557a",
Lang: "fr",
},
result)
}

View File

@@ -43,7 +43,7 @@ func (i *InventaireBookResult) UnmarshalJSON(b []byte) error {
if err != nil { if err != nil {
return err return err
} }
if inventaireEntity.WdId == i.ID { if ("wd:" + inventaireEntity.WdId) == i.ID {
title, err := findLangageField(inventaireEntity.Labels, i.Lang) title, err := findLangageField(inventaireEntity.Labels, i.Lang)
if err != nil { if err != nil {
return err return err
@@ -73,20 +73,28 @@ func (i *InventaireBookResult) UnmarshalJSON(b []byte) error {
} }
func findLangageField(multipleMessageFields map[string]json.RawMessage, lang string) (string, error) { func findLangageField(multipleMessageFields map[string]json.RawMessage, lang string) (string, error) {
fieldToParse, ok := multipleMessageFields[lang] if len(multipleMessageFields) == 0 {
if ok { return "", errors.New("empty multilang field")
}
var fieldToParse json.RawMessage
var ok bool
fieldToParse, ok = multipleMessageFields[lang]
if !ok {
for _, field := range multipleMessageFields {
fieldToParse = field
break
}
}
var parsedField string var parsedField string
err := json.Unmarshal(fieldToParse, &parsedField) err := json.Unmarshal(fieldToParse, &parsedField)
if err != nil { if err != nil {
return "", err return "", err
} }
return parsedField, err return parsedField, err
} else {
return "", errors.New("multi lang field could not be parsed")
}
} }
func CallInventaireBook(inventaireId string, lang string) (InventaireBookResult, error) { func callInventaireBook(inventaireId string, lang string) (InventaireBookResult, error) {
queryResult := InventaireBookResult{ID: inventaireId, Lang: lang} queryResult := InventaireBookResult{ID: inventaireId, Lang: lang}
u, err := computeInventaireApiUrl("entities") u, err := computeInventaireApiUrl("entities")
if err != nil { if err != nil {
@@ -95,7 +103,7 @@ func CallInventaireBook(inventaireId string, lang string) (InventaireBookResult,
callapiutils.AddQueryParam(u, "action", "by-uris") callapiutils.AddQueryParam(u, "action", "by-uris")
callapiutils.AddQueryParam(u, "relatives", "wdt:P50") callapiutils.AddQueryParam(u, "relatives", "wdt:P50")
callapiutils.AddQueryParam(u, "lang", lang) callapiutils.AddQueryParam(u, "lang", lang)
callapiutils.AddQueryParam(u, "uris", "wd:"+inventaireId) callapiutils.AddQueryParam(u, "uris", inventaireId)
err = callapiutils.FetchAndParseResult(u, &queryResult) err = callapiutils.FetchAndParseResult(u, &queryResult)
return queryResult, err return queryResult, err

View File

@@ -2,38 +2,19 @@ package inventaire
import ( import (
"encoding/json" "encoding/json"
"math" "net/url"
"sort"
"strings" "strings"
"git.artlef.fr/PersonalLibraryManager/internal/callapiutils" "git.artlef.fr/PersonalLibraryManager/internal/callapiutils"
) )
type InventaireEditionResult struct {
Results []InventaireEditionResultBook `json:"results"`
Count int64 `json:"count"`
}
type InventaireEditionResultBook struct {
Id string `json:"uri"`
Title string `json:"title"`
ISBN string `json:"isbn"`
Publisher string `json:"publisher"`
ReleaseDate string `json:"date"`
Image string `json:"image"`
Lang string `json:"lang"`
}
type inventaireReverseClaimsResult struct {
Uris []string `json:"uris"`
}
type inventaireEditionQueryResult struct { type inventaireEditionQueryResult struct {
Entities []inventaireEditionQueryEntity Entities []inventaireEditionQueryEntity
} }
type inventaireEditionQueryEntity struct { type inventaireEditionQueryEntity struct {
WdId string WdId string
WorkId string
EditionId string EditionId string
Title string Title string
ISBN string ISBN string
@@ -66,6 +47,10 @@ func (i *inventaireEditionQueryResult) UnmarshalJSON(b []byte) error {
return err return err
} }
if parsedEntity.Type == "edition" { if parsedEntity.Type == "edition" {
workId, err := parseStringArrayFieldInJsonRaw(parsedEntity.Claims, "wdt:P629")
if err != nil {
return err
}
editionId, err := parseStringArrayFieldInJsonRaw(parsedEntity.Claims, "wdt:P123") editionId, err := parseStringArrayFieldInJsonRaw(parsedEntity.Claims, "wdt:P123")
if err != nil { if err != nil {
return err return err
@@ -95,6 +80,7 @@ func (i *inventaireEditionQueryResult) UnmarshalJSON(b []byte) error {
} }
i.Entities = append(i.Entities, inventaireEditionQueryEntity{ i.Entities = append(i.Entities, inventaireEditionQueryEntity{
WdId: parsedEntity.WdId, WdId: parsedEntity.WdId,
WorkId: workId,
EditionId: editionId, EditionId: editionId,
Title: label, Title: label,
ISBN: isbn, ISBN: isbn,
@@ -125,80 +111,26 @@ func parseStringArrayFieldInJsonRaw(jsonRawMap map[string]json.RawMessage, key s
return s, err return s, err
} }
func CallInventaireEdition(inventaireId string, lang string, limit int, offset int) (InventaireEditionResult, error) { func callInventaireEditionEntities(uris []string) (inventaireEditionQueryResult, error) {
var queryResult InventaireEditionResult var queryResult inventaireEditionQueryResult
uris, err := callInventaireUris(inventaireId) u, err := getInventaireEditionEntitiesUri(strings.Join(uris, "|"))
if err != nil { if err != nil {
return queryResult, err return queryResult, err
} }
queryResult.Count = int64(len(uris.Uris))
sort.Strings(uris.Uris)
limitedUris := uris.Uris
if limit != 0 {
l := len(uris.Uris)
startIndex := int(math.Min(float64(offset), float64(l)))
endIndex := int(math.Min(float64(limit+offset), float64(l)))
limitedUris = uris.Uris[startIndex:endIndex]
}
editionEntities, err := callInventaireEditionEntities(limitedUris)
if err != nil {
return queryResult, err
}
sortedEntities := editionEntities.Entities
sort.Slice(sortedEntities, func(i, j int) bool {
return sortedEntities[i].Uri < sortedEntities[j].Uri
})
for _, entity := range sortedEntities {
publisher := ""
if entity.EditionId != "" {
publisher, err = callInventairePublisherGetName(entity.EditionId, lang)
if err != nil {
return queryResult, err
}
}
queryResult.Results = append(queryResult.Results, InventaireEditionResultBook{
Id: entity.Uri,
ISBN: entity.ISBN,
Title: entity.Title,
ReleaseDate: entity.ReleaseDate,
Image: entity.Image,
Publisher: publisher,
Lang: entity.Lang,
})
}
return queryResult, err
}
func callInventaireUris(inventaireId string) (inventaireReverseClaimsResult, error) {
var queryResult inventaireReverseClaimsResult
u, err := computeInventaireApiUrl("entities")
if err != nil {
return queryResult, err
}
callapiutils.AddQueryParam(u, "action", "reverse-claims")
callapiutils.AddQueryParam(u, "property", "wdt:P629")
callapiutils.AddQueryParam(u, "value", inventaireId)
err = callapiutils.FetchAndParseResult(u, &queryResult) err = callapiutils.FetchAndParseResult(u, &queryResult)
return queryResult, err return queryResult, err
} }
func callInventaireEditionEntities(uris []string) (inventaireEditionQueryResult, error) { func getInventaireEditionEntitiesUri(uris string) (*url.URL, error) {
var queryResult inventaireEditionQueryResult
u, err := computeInventaireApiUrl("entities") u, err := computeInventaireApiUrl("entities")
if err != nil { if err != nil {
return queryResult, err return u, err
} }
callapiutils.AddQueryParam(u, "action", "by-uris") callapiutils.AddQueryParam(u, "action", "by-uris")
callapiutils.AddQueryParam(u, "uris", strings.Join(uris, "|")) callapiutils.AddQueryParam(u, "uris", uris)
err = callapiutils.FetchAndParseResult(u, &queryResult) return u, err
return queryResult, err
} }
type inventaireEditionPublisherResult struct { type inventaireEditionPublisherResult struct {

View File

@@ -0,0 +1,88 @@
package inventaire
import (
"math"
"sort"
"git.artlef.fr/PersonalLibraryManager/internal/callapiutils"
)
type InventaireEditionResult struct {
Results []InventaireEditionResultBook `json:"results"`
Count int64 `json:"count"`
}
type InventaireEditionResultBook struct {
Id string `json:"uri"`
Title string `json:"title"`
ISBN string `json:"isbn"`
Publisher string `json:"publisher"`
ReleaseDate string `json:"date"`
Image string `json:"image"`
Lang string `json:"lang"`
}
type inventaireReverseClaimsResult struct {
Uris []string `json:"uris"`
}
func CallInventaireEditionFromWork(workId string, lang string, limit int, offset int) (InventaireEditionResult, error) {
var queryResult InventaireEditionResult
uris, err := callInventaireUris(workId)
if err != nil {
return queryResult, err
}
queryResult.Count = int64(len(uris.Uris))
sort.Strings(uris.Uris)
limitedUris := uris.Uris
if limit != 0 {
l := len(uris.Uris)
startIndex := int(math.Min(float64(offset), float64(l)))
endIndex := int(math.Min(float64(limit+offset), float64(l)))
limitedUris = uris.Uris[startIndex:endIndex]
}
editionEntities, err := callInventaireEditionEntities(limitedUris)
if err != nil {
return queryResult, err
}
sortedEntities := editionEntities.Entities
sort.Slice(sortedEntities, func(i, j int) bool {
return sortedEntities[i].Uri < sortedEntities[j].Uri
})
for _, entity := range sortedEntities {
publisher := ""
if entity.EditionId != "" {
publisher, err = callInventairePublisherGetName(entity.EditionId, lang)
if err != nil {
return queryResult, err
}
}
queryResult.Results = append(queryResult.Results, InventaireEditionResultBook{
Id: entity.Uri,
ISBN: entity.ISBN,
Title: entity.Title,
ReleaseDate: entity.ReleaseDate,
Image: entity.Image,
Publisher: publisher,
Lang: entity.Lang,
})
}
return queryResult, err
}
func callInventaireUris(workId string) (inventaireReverseClaimsResult, error) {
var queryResult inventaireReverseClaimsResult
u, err := computeInventaireApiUrl("entities")
if err != nil {
return queryResult, err
}
callapiutils.AddQueryParam(u, "action", "reverse-claims")
callapiutils.AddQueryParam(u, "property", "wdt:P629")
callapiutils.AddQueryParam(u, "value", workId)
err = callapiutils.FetchAndParseResult(u, &queryResult)
return queryResult, err
}

View File

@@ -0,0 +1,62 @@
package inventaire
import (
"fmt"
)
type InventaireEditionDetailedSingleResult struct {
Id string
Title string
Description string
Author *InventaireAuthorResult
ISBN string
Publisher string
ReleaseDate string
Image string
Lang string
}
func CallInventaireEdition(inventaireId string, lang string) (InventaireEditionDetailedSingleResult, error) {
var result InventaireEditionDetailedSingleResult
editionQueryResults, err := callInventaireEditionEntities([]string{inventaireId})
if err != nil {
return result, err
}
var editionQueryResult inventaireEditionQueryEntity
if len(editionQueryResults.Entities) < 1 {
return result, fmt.Errorf("No edition found on inventaire for id %s", inventaireId)
}
editionQueryResult = editionQueryResults.Entities[0]
var publisher string
if editionQueryResult.EditionId != "" {
publisher, err = callInventairePublisherGetName(editionQueryResult.EditionId, lang)
if err != nil {
return result, err
}
}
var author *InventaireAuthorResult
var description string
if editionQueryResult.WorkId != "" {
workQueryResult, err := callInventaireBook(editionQueryResult.WorkId, lang)
if err != nil {
return result, err
}
author = workQueryResult.Author
description = workQueryResult.Description
}
result = InventaireEditionDetailedSingleResult{
Id: editionQueryResult.Uri,
Title: editionQueryResult.Title,
Author: author,
Description: description,
ISBN: editionQueryResult.ISBN,
Publisher: publisher,
ReleaseDate: editionQueryResult.ReleaseDate,
Image: editionQueryResult.Image,
Lang: editionQueryResult.Lang,
}
return result, err
}

View File

@@ -26,12 +26,12 @@ func PostImportBookHandler(ac appcontext.AppContext) {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err) myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return return
} }
inventaireBook, err := inventaire.CallInventaireBook(request.InventaireID, request.Lang) inventaireEdition, err := inventaire.CallInventaireEdition(request.InventaireID, request.Lang)
if err != nil { if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err) myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return return
} }
book, err := saveInventaireBookToDb(ac, inventaireBook, &user) book, err := saveInventaireBookToDb(ac, inventaireEdition, &user)
if err != nil { if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err) myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return return
@@ -39,15 +39,15 @@ func PostImportBookHandler(ac appcontext.AppContext) {
ac.C.JSON(200, gin.H{"id": book.ID}) ac.C.JSON(200, gin.H{"id": book.ID})
} }
func saveInventaireBookToDb(ac appcontext.AppContext, inventaireBook inventaire.InventaireBookResult, user *model.User) (*model.Book, error) { func saveInventaireBookToDb(ac appcontext.AppContext, inventaireEdition inventaire.InventaireEditionDetailedSingleResult, user *model.User) (*model.Book, error) {
author, err := fetchOrCreateInventaireAuthor(ac, inventaireBook.Author) author, err := fetchOrCreateInventaireAuthor(ac, inventaireEdition.Author)
if err != nil { if err != nil {
return nil, err return nil, err
} }
book := model.Book{ book := model.Book{
Title: inventaireBook.Title, Title: inventaireEdition.Title,
SmallDescription: inventaireBook.Description, SmallDescription: inventaireEdition.Description,
InventaireID: inventaireBook.ID, InventaireID: inventaireEdition.Id,
Author: *author, Author: *author,
AddedBy: *user, AddedBy: *user,
} }

View File

@@ -29,7 +29,7 @@ func GetInventaireBooks(ac appcontext.AppContext) {
return return
} }
inventaireEditionResult, err := inventaire.CallInventaireEdition(workId, params.Lang, limit, offset) inventaireEditionResult, err := inventaire.CallInventaireEditionFromWork(workId, params.Lang, limit, offset)
if err != nil { if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err) myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return return