Skip to content

Commit

Permalink
initial service
Browse files Browse the repository at this point in the history
  • Loading branch information
theorm committed Jan 17, 2025
1 parent b0eb55a commit 2daad3f
Show file tree
Hide file tree
Showing 16 changed files with 366 additions and 53 deletions.
8 changes: 8 additions & 0 deletions src/models/generated/common.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,10 @@ export interface SolrServerNamespaceConfiguration {
* Solr index name
*/
index: string;
/**
* Version of the data schema used in the index. Optional.
*/
schemaVersion?: string;
}
export interface ProxyConfig {
/**
Expand Down Expand Up @@ -514,4 +518,8 @@ export interface SolrServerNamespaceConfiguration {
* Solr index name
*/
index: string;
/**
* Version of the data schema used in the index. Optional.
*/
schemaVersion?: string;
}
28 changes: 5 additions & 23 deletions src/models/generated/schemas.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -879,36 +879,18 @@ export interface Image {
* The unique identifier of the image
*/
uid: string;
/**
* The unique identifier of the issue that the image belongs to.
*/
issueUid: string;
/**
* The unique identifier of the content item that the image belongs to.
*/
contentItemUid: string;
contentItemUid?: string;
/**
* The URL of the image preview
*/
previewUrl: string;
/**
* The URL of the IIIF info.json file
*/
iiifInfoUrl: string;
permissions?: ContentPermissions;
}
/**
* Content item permissions
*/
export interface ContentPermissions {
/**
* Bitmap representing the 'explore' permissions of the content item
*/
exploreBitmap?: number;
/**
* Bitmap representing the 'get transcript' permissions of the content item
*/
getTranscriptBitmap?: number;
/**
* Bitmap representing the 'get images' permissions of the content item
*/
getImagesBitmap?: number;
}


Expand Down
23 changes: 23 additions & 0 deletions src/models/generated/schemasPublic.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,29 @@ export interface Freeform {
}


/**
* An image from a content item
*/
export interface Image {
/**
* The unique identifier of the image
*/
uid: string;
/**
* The unique identifier of the issue that the image belongs to.
*/
issueUid: string;
/**
* The unique identifier of the content item that the image belongs to.
*/
contentItemUid?: string;
/**
* The URL of the image preview
*/
previewUrl: string;
}


/**
* A media source is what a content item belongs to. This can be a newspaper, a TV or a radio station, etc.
*/
Expand Down
41 changes: 41 additions & 0 deletions src/models/generated/solr.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

/* eslint-disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run json-schema-to-typescript to regenerate this file.
*/


/**
* Image Solr document in Impresso v2
*/
export interface Image {
id?: string;
meta_journal_s?: string;
meta_year_i?: number;
meta_day_i?: number;
meta_ed_s?: string;
meta_issue_id_s?: string;
meta_date_dt?: string;
linked_ci_s?: string;
reading_order_i?: number;
caption_txt?: string[];
item_type_s?: string;
page_nb_is?: number[];
coords_is?: number[];
front_b?: boolean;
iiif_url_s?: string;
cc_b?: boolean;
rights_data_domain_s?: string;
rights_copyright_s?: string;
rights_perm_use_explore_plain?: string[];
rights_perm_use_get_tr_plain?: string[];
rights_perm_use_get_img_plain?: string[];
rights_bm_explore_l?: number;
rights_bm_get_tr_l?: number;
rights_bm_get_img_l?: number;
dinov2_emb_v1024?: number[];
openclip_emb_v768?: number[];
_version_?: number;
}
4 changes: 4 additions & 0 deletions src/schema/common/solrConfiguration.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@
"index": {
"type": "string",
"description": "Solr index name"
},
"schemaVersion": {
"type": "string",
"description": "Version of the data schema used in the index. Optional."
}
},
"required": ["namespaceId", "serverId", "index"]
Expand Down
14 changes: 5 additions & 9 deletions src/schema/schemas/Image.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
"type": "string",
"description": "The unique identifier of the image"
},
"issueUid": {
"type": "string",
"description": "The unique identifier of the issue that the image belongs to."
},
"contentItemUid": {
"type": "string",
"description": "The unique identifier of the content item that the image belongs to."
Expand All @@ -17,15 +21,7 @@
"type": "string",
"format": "uri",
"description": "The URL of the image preview"
},
"iiifInfoUrl": {
"type": "string",
"format": "uri",
"description": "The URL of the IIIF info.json file"
},
"permissions": {
"$ref": "./ContentPermissions.json"
}
},
"required": ["uid", "contentItemUid", "previewUrl", "iiifInfoUrl"]
"required": ["uid", "issueUid", "previewUrl"]
}
27 changes: 27 additions & 0 deletions src/schema/schemasPublic/Image.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Image",
"description": "An image from a content item",
"type": "object",
"additionalProperties": false,
"properties": {
"uid": {
"type": "string",
"description": "The unique identifier of the image"
},
"issueUid": {
"type": "string",
"description": "The unique identifier of the issue that the image belongs to."
},
"contentItemUid": {
"type": "string",
"description": "The unique identifier of the content item that the image belongs to."
},
"previewUrl": {
"type": "string",
"format": "uri",
"description": "The URL of the image preview"
}
},
"required": ["uid", "issueUid", "previewUrl"]
}
60 changes: 60 additions & 0 deletions src/schema/solr/Image.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "Image",
"description": "Image Solr document in Impresso v2",
"properties": {
"id": { "type": "string" },
"meta_journal_s": { "type": "string" },
"meta_year_i": { "type": "integer" },
"meta_day_i": { "type": "integer" },
"meta_ed_s": { "type": "string" },
"meta_issue_id_s": { "type": "string" },
"meta_date_dt": { "type": "string", "format": "date-time" },
"linked_ci_s": { "type": "string" },
"reading_order_i": { "type": "integer" },
"caption_txt": {
"type": "array",
"items": { "type": "string" }
},
"item_type_s": { "type": "string" },
"page_nb_is": {
"type": "array",
"items": { "type": "integer" }
},
"coords_is": {
"type": "array",
"items": { "type": "integer" }
},
"front_b": { "type": "boolean" },
"iiif_url_s": { "type": "string", "format": "uri" },
"cc_b": { "type": "boolean" },
"rights_data_domain_s": { "type": "string" },
"rights_copyright_s": { "type": "string" },
"rights_perm_use_explore_plain": {
"type": "array",
"items": { "type": "string" }
},
"rights_perm_use_get_tr_plain": {
"type": "array",
"items": { "type": "string" }
},
"rights_perm_use_get_img_plain": {
"type": "array",
"items": { "type": "string" }
},
"rights_bm_explore_l": { "type": "integer" },
"rights_bm_get_tr_l": { "type": "integer" },
"rights_bm_get_img_l": { "type": "integer" },
"dinov2_emb_v1024": {
"type": "array",
"items": { "type": "number" }
},
"openclip_emb_v768": {
"type": "array",
"items": { "type": "number" }
},
"_version_": { "type": "number" }
},
"additionalProperties": false
}
2 changes: 1 addition & 1 deletion src/scripts/generate-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const banner = `

const basePath = './src/schema'
const outputPath = './src/models/generated'
const schemaBits = ['schemas', 'schemasPublic', 'shared', 'parameters', 'requestBodies', 'responses', 'common']
const schemaBits = ['schemas', 'schemasPublic', 'shared', 'parameters', 'requestBodies', 'responses', 'common', 'solr']
const directories = fs
.readdirSync(basePath)
.filter(item => {
Expand Down
2 changes: 1 addition & 1 deletion src/services/images/images-v1.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const {
} = require('../../solr')

export default class Service {
constructor({ app = null, name = '' }) {
constructor({ app, name = '' }) {
this.app = app
this.name = name
this.sequelizeClient = this.app.get('sequelizeClient')
Expand Down
86 changes: 86 additions & 0 deletions src/services/images/images.class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { NotFound } from '@feathersjs/errors'
import { ClientService, Id, Params } from '@feathersjs/feathers'
import { SimpleSolrClient } from '../../internalServices/simpleSolr'
import { PublicFindResponse } from '../../models/common'
import { Image } from '../../models/generated/schemas'
import { Image as ImageDocument } from '../../models/generated/solr'
import { SolrNamespaces } from '../../solr'

const DefaultLimit = 10
const ImageSimilarityVectorField: keyof ImageDocument = 'dinov2_emb_v1024'

export interface FindQuery {
similar_to_image_id?: string
limit?: number
offset?: number
}

export class Images implements Pick<ClientService<Image, unknown, unknown, PublicFindResponse<Image>>, 'find' | 'get'> {
constructor(private readonly solrClient: SimpleSolrClient) {}

async find(params?: Params<FindQuery>): Promise<PublicFindResponse<Image>> {
const limit = params?.query?.limit ?? DefaultLimit
const offset = params?.query?.offset ?? 0

const queryParts: string[] = []

if (params?.query?.similar_to_image_id) {
const referenceId = params.query.similar_to_image_id
const referenceImage = await this.getImageDocument(referenceId, [ImageSimilarityVectorField])
const vector = referenceImage?.[ImageSimilarityVectorField] as number[]

if (referenceImage == null || vector == null)
return {
data: [],
pagination: {
limit: limit ?? 0,
offset: offset ?? 0,
total: 0,
},
}

queryParts.push(`{!knn f=${ImageSimilarityVectorField} topK=${limit}}${JSON.stringify(vector)}`)
}

const query = queryParts.length > 0 ? queryParts.join(' AND ') : '*:*'

const results = await this.solrClient.select<ImageDocument>(SolrNamespaces.Images, {
body: {
query,
limit,
offset,
},
})

return {
data: results?.response?.docs?.map(toImage) ?? [],
pagination: {
limit: 0,
offset: 0,
total: 0,
},
}
}

async get(id: Id, params?: Params): Promise<Image> {
const imageDoc = await this.getImageDocument(String(id))
if (imageDoc == null) throw new NotFound(`Image with id ${id} not found`)
return toImage(imageDoc)
}

async getImageDocument(id: string, fields?: (keyof ImageDocument)[]): Promise<ImageDocument | undefined> {
const result = await this.solrClient.selectOne<ImageDocument>(SolrNamespaces.Images, {
body: { query: `id:${id}`, limit: 1, fields: fields != null ? fields?.join(',') : undefined },
})
return result
}
}

const toImage = (doc: ImageDocument): Image => {
return {
uid: doc.id!,
...(doc.linked_ci_s != null ? { contentItemUid: doc.linked_ci_s } : {}),
issueUid: doc.meta_issue_id_s!,
previewUrl: doc.iiif_url_s!,
}
}
10 changes: 10 additions & 0 deletions src/services/images/images.hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { HookMap } from '@feathersjs/feathers'
import { authenticateAround as authenticate } from '../../hooks/authenticate'
import { rateLimit } from '../../hooks/rateLimiter'
import { AppServices, ImpressoApplication } from '../../types'

export default {
around: {
all: [authenticate({ allowUnauthenticated: true }), rateLimit()],
},
} satisfies HookMap<ImpressoApplication, AppServices>
Loading

0 comments on commit 2daad3f

Please sign in to comment.