This commit is contained in:
Stefan Hardegger
2026-07-20 11:49:52 +02:00
parent d0344813a3
commit 7722c8348c
7 changed files with 233 additions and 1 deletions

View File

@@ -63,6 +63,7 @@ storycove:
cores: cores:
stories: ${SOLR_STORIES_CORE:storycove_stories} stories: ${SOLR_STORIES_CORE:storycove_stories}
authors: ${SOLR_AUTHORS_CORE:storycove_authors} authors: ${SOLR_AUTHORS_CORE:storycove_authors}
collections: ${SOLR_COLLECTIONS_CORE:storycove_collections}
# Connection settings # Connection settings
connection: connection:

View File

@@ -6,6 +6,7 @@ USER root
# Copy Solr configurations into the image # Copy Solr configurations into the image
COPY ./solr/stories /opt/solr-9.9.0/server/solr/configsets/storycove_stories COPY ./solr/stories /opt/solr-9.9.0/server/solr/configsets/storycove_stories
COPY ./solr/authors /opt/solr-9.9.0/server/solr/configsets/storycove_authors COPY ./solr/authors /opt/solr-9.9.0/server/solr/configsets/storycove_authors
COPY ./solr/collections /opt/solr-9.9.0/server/solr/configsets/storycove_collections
# Create initialization script using the precreate-core pattern # Create initialization script using the precreate-core pattern
COPY <<EOF /docker-entrypoint-initdb.d/init-cores.sh COPY <<EOF /docker-entrypoint-initdb.d/init-cores.sh
@@ -15,12 +16,16 @@ echo "StoryCove: Initializing cores..."
# Create cores on first startup (no-op if they already exist) # Create cores on first startup (no-op if they already exist)
precreate-core storycove_stories /opt/solr-9.9.0/server/solr/configsets/storycove_stories precreate-core storycove_stories /opt/solr-9.9.0/server/solr/configsets/storycove_stories
precreate-core storycove_authors /opt/solr-9.9.0/server/solr/configsets/storycove_authors precreate-core storycove_authors /opt/solr-9.9.0/server/solr/configsets/storycove_authors
precreate-core storycove_collections /opt/solr-9.9.0/server/solr/configsets/storycove_collections
# Always sync schema files from the image so deployments pick up schema changes # Always sync schema files from the image so deployments pick up schema changes
cp -f /opt/solr-9.9.0/server/solr/configsets/storycove_stories/conf/managed-schema /var/solr/data/storycove_stories/conf/managed-schema cp -f /opt/solr-9.9.0/server/solr/configsets/storycove_stories/conf/managed-schema /var/solr/data/storycove_stories/conf/managed-schema
if [ -f /opt/solr-9.9.0/server/solr/configsets/storycove_authors/conf/managed-schema ]; then if [ -f /opt/solr-9.9.0/server/solr/configsets/storycove_authors/conf/managed-schema ]; then
cp -f /opt/solr-9.9.0/server/solr/configsets/storycove_authors/conf/managed-schema /var/solr/data/storycove_authors/conf/managed-schema cp -f /opt/solr-9.9.0/server/solr/configsets/storycove_authors/conf/managed-schema /var/solr/data/storycove_authors/conf/managed-schema
fi fi
if [ -f /opt/solr-9.9.0/server/solr/configsets/storycove_collections/conf/managed-schema ]; then
cp -f /opt/solr-9.9.0/server/solr/configsets/storycove_collections/conf/managed-schema /var/solr/data/storycove_collections/conf/managed-schema
fi
echo "StoryCove: Core initialization complete!" echo "StoryCove: Core initialization complete!"
EOF EOF

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Solr Schema for StoryCove Collections Core
-->
<schema name="storycove-collections" version="1.6">
<!-- Field Types -->
<fieldType name="string" class="solr.StrField" sortMissingLast="true" />
<fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<fieldType name="text_enhanced" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.EdgeNGramFilterFactory" minGramSize="1" maxGramSize="15"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<fieldType name="pint" class="solr.IntPointField" docValues="true"/>
<fieldType name="plong" class="solr.LongPointField" docValues="true"/>
<fieldType name="pdouble" class="solr.DoublePointField" docValues="true"/>
<fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/>
<fieldType name="pdate" class="solr.DatePointField" docValues="true"/>
<fieldType name="strings" class="solr.StrField" sortMissingLast="true" multiValued="true" docValues="true"/>
<fieldType name="random" class="solr.RandomSortField" indexed="true"/>
<!-- Fields -->
<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />
<field name="_version_" type="plong" indexed="false" stored="false"/>
<!-- Core Collection Fields -->
<field name="name" type="text_enhanced" indexed="true" stored="true" required="true"/>
<field name="description" type="text_general" indexed="true" stored="true"/>
<field name="rating" type="pint" indexed="true" stored="true"/>
<field name="coverImagePath" type="string" indexed="false" stored="true"/>
<field name="isArchived" type="boolean" indexed="true" stored="true"/>
<!-- Derived Stats -->
<field name="storyCount" type="pint" indexed="true" stored="true"/>
<field name="totalWordCount" type="plong" indexed="true" stored="true"/>
<field name="estimatedReadingTime" type="pint" indexed="true" stored="true"/>
<field name="averageStoryRating" type="pdouble" indexed="true" stored="true"/>
<!-- Tag Fields -->
<field name="tagNames" type="strings" indexed="true" stored="true"/>
<!-- Multi-tenant Library Separation -->
<field name="libraryId" type="string" indexed="true" stored="true" required="false" default="default"/>
<!-- Timestamp Fields -->
<field name="createdAt" type="pdate" indexed="true" stored="true"/>
<field name="updatedAt" type="pdate" indexed="true" stored="true"/>
<!-- Combined search field for general queries -->
<field name="text" type="text_general" indexed="true" stored="false" multiValued="true"/>
<copyField source="name" dest="text"/>
<copyField source="description" dest="text"/>
<copyField source="tagNames" dest="text"/>
<dynamicField name="random_*" type="random" indexed="true" stored="false"/>
<uniqueKey>id</uniqueKey>
</schema>

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Solr Configuration for StoryCove Collections Core
-->
<config>
<luceneMatchVersion>9.9.0</luceneMatchVersion>
<dataDir>${solr.data.dir:}</dataDir>
<directoryFactory name="DirectoryFactory"
class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
<codecFactory class="solr.SchemaCodecFactory"/>
<indexConfig>
<lockType>${solr.lock.type:native}</lockType>
<infoStream>true</infoStream>
</indexConfig>
<jmx />
<updateHandler class="solr.DirectUpdateHandler2">
<updateLog>
<str name="dir">${solr.ulog.dir:}</str>
<int name="numVersionBuckets">${solr.ulog.numVersionBuckets:65536}</int>
</updateLog>
<autoCommit>
<maxTime>15000</maxTime>
<openSearcher>false</openSearcher>
</autoCommit>
<autoSoftCommit>
<maxTime>1000</maxTime>
</autoSoftCommit>
</updateHandler>
<query>
<maxBooleanClauses>1024</maxBooleanClauses>
<filterCache class="solr.CaffeineCache"
size="512"
initialSize="512"
autowarmCount="0"/>
<queryResultCache class="solr.CaffeineCache"
size="512"
initialSize="512"
autowarmCount="0"/>
<documentCache class="solr.CaffeineCache"
size="512"
initialSize="512"
autowarmCount="0"/>
<enableLazyFieldLoading>true</enableLazyFieldLoading>
</query>
<requestDispatcher handleSelect="false">
<requestParsers enableRemoteStreaming="true"
multipartUploadLimitInKB="2048000"
formdataUploadLimitInKB="2048"
addHttpRequestToContext="false"/>
<httpCaching never304="true" />
</requestDispatcher>
<requestHandler name="/select" class="solr.SearchHandler">
<lst name="defaults">
<str name="echoParams">explicit</str>
<int name="rows">10</int>
<str name="df">text</str>
<str name="wt">json</str>
<str name="indent">true</str>
<str name="hl">true</str>
<str name="hl.fl">name,description</str>
<str name="hl.simple.pre">&lt;em&gt;</str>
<str name="hl.simple.post">&lt;/em&gt;</str>
<str name="hl.fragsize">150</str>
<str name="facet">true</str>
<str name="facet.field">tagNames</str>
<str name="facet.field">isArchived</str>
<str name="facet.mincount">1</str>
</lst>
</requestHandler>
<requestHandler name="/update" class="solr.UpdateRequestHandler" />
<requestHandler name="/admin/ping" class="solr.PingRequestHandler">
<lst name="invariants">
<str name="q">*:*</str>
</lst>
<lst name="defaults">
<str name="echoParams">all</str>
</lst>
</requestHandler>
<queryResponseWriter name="json" class="solr.JSONResponseWriter">
<str name="content-type">application/json; charset=UTF-8</str>
</queryResponseWriter>
</config>

View File

@@ -0,0 +1,34 @@
# English stopwords for author search
a
an
and
are
as
at
be
but
by
for
if
in
into
is
it
no
not
of
on
or
such
that
the
their
then
there
these
they
this
to
was
will
with

View File

@@ -0,0 +1,9 @@
# Synonyms for author search
# Format: word1,word2,word3 => synonym1,synonym2
writer,author,novelist
pen name,pseudonym,alias
prolific,productive
acclaimed,famous,renowned
bestselling,popular
contemporary,modern
classic,traditional

View File

@@ -78,7 +78,7 @@
<field name="wordCount" type="pint" indexed="true" stored="true"/> <field name="wordCount" type="pint" indexed="true" stored="true"/>
<field name="rating" type="pint" indexed="true" stored="true"/> <field name="rating" type="pint" indexed="true" stored="true"/>
<field name="averageStoryRating" type="pdouble" indexed="true" stored="true"/> <field name="averageStoryRating" type="pdouble" indexed="true" stored="true"/>
<field name="volume" type="pint" indexed="true" stored="true"/> <field name="volume" type="pdouble" indexed="true" stored="true"/>
<!-- Multi-tenant Library Separation --> <!-- Multi-tenant Library Separation -->
<field name="libraryId" type="string" indexed="true" stored="true" required="false" default="default"/> <field name="libraryId" type="string" indexed="true" stored="true" required="false" default="default"/>