fix series number and improve author view

This commit is contained in:
Stefan Hardegger
2026-07-20 12:51:20 +02:00
parent ddfc5aef99
commit d910ad8fa8
4 changed files with 79 additions and 27 deletions

View File

@@ -1394,6 +1394,10 @@ public class SolrService {
}
public void recreateCores() throws IOException {
if (!isAvailable()) {
throw new IOException("Solr is not available");
}
List<String> coreNames = Arrays.asList(
properties.getCores().getStories(),
properties.getCores().getAuthors(),
@@ -1403,20 +1407,13 @@ public class SolrService {
for (String coreName : coreNames) {
try {
logger.info("Clearing Lucene index for core: {}", coreName);
// Unload the core and delete only the Lucene index files.
// deleteIndex=true removes data/index/ only; conf/ and core.properties are preserved.
// deleteDataDir=false keeps the data directory so the core can be reloaded in place.
org.apache.solr.client.solrj.request.CoreAdminRequest.unloadCore(
coreName, true, false, solrClient);
logger.info("Unloaded core (index deleted): {}", coreName);
// Reload the core from its existing conf/ directory with a fresh empty index.
org.apache.solr.client.solrj.request.CoreAdminRequest.reloadCore(coreName, solrClient);
logger.info("Reloaded core with fresh index: {}", coreName);
} catch (SolrServerException e) {
logger.warn("Could not clear/reload core {} (may not exist yet): {}", coreName, e.getMessage());
// Delete all documents and hard-commit so the index is empty immediately.
// Using deleteByQuery keeps the core registered in Solr — no restart needed.
solrClient.deleteByQuery(coreName, "*:*");
solrClient.commit(coreName);
logger.info("Successfully cleared index for core: {}", coreName);
} catch (Exception e) {
logger.warn("Could not clear core {} (may not exist yet): {}", coreName, e.getMessage());
}
}
}

View File

@@ -489,7 +489,8 @@ export default function AddStoryContent() {
<Input
label="Volume/Part (optional)"
type="number"
min="1"
min="0.1"
step="any"
value={formData.volume}
onChange={handleInputChange('volume')}
placeholder="Enter volume/part number"

View File

@@ -11,6 +11,39 @@ import Button from '../../../components/ui/Button';
import StoryCard from '../../../components/stories/StoryCard';
import LoadingSpinner from '../../../components/ui/LoadingSpinner';
type DisplayItem =
| { type: 'standalone'; story: Story }
| { type: 'series'; seriesId: string; seriesName: string; stories: Story[] };
function buildDisplayItems(stories: Story[], authorId: string): DisplayItem[] {
const authorStories = stories.filter(s => s.authorId === authorId);
const sorted = [...authorStories].sort(
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
);
const items: DisplayItem[] = [];
const addedSeries = new Set<string>();
for (const story of sorted) {
if (story.seriesId) {
if (!addedSeries.has(story.seriesId)) {
const seriesStories = sorted.filter(s => s.seriesId === story.seriesId);
items.push({
type: 'series',
seriesId: story.seriesId,
seriesName: story.seriesName!,
stories: seriesStories,
});
addedSeries.add(story.seriesId);
}
} else {
items.push({ type: 'standalone', story });
}
}
return items;
}
export default function AuthorDetailPage() {
const params = useParams();
const router = useRouter();
@@ -220,18 +253,38 @@ export default function AuthorDetailPage() {
</div>
) : (
<div className="space-y-4">
{stories.map((story) => (
{buildDisplayItems(stories, authorId).map((item) => {
if (item.type === 'standalone') {
return (
<StoryCard
key={item.story.id}
story={item.story}
viewMode="list"
onUpdate={() => window.location.reload()}
/>
);
}
return (
<div key={item.seriesId}>
<div className="flex items-center gap-2 mb-2 px-1">
<span className="text-xs font-semibold theme-text uppercase tracking-wide opacity-60">Series</span>
<span className="font-semibold theme-header">{item.seriesName}</span>
<span className="text-xs theme-text opacity-60">({item.stories.length} {item.stories.length === 1 ? 'part' : 'parts'})</span>
</div>
<div className="space-y-4 pl-4 border-l-2 border-gray-300 dark:border-gray-600">
{item.stories.map((story) => (
<StoryCard
key={story.id}
story={story}
viewMode="list"
onUpdate={() => {
// Reload stories after update
window.location.reload();
}}
onUpdate={() => window.location.reload()}
/>
))}
</div>
</div>
);
})}
</div>
)}
</div>
</div>

View File

@@ -439,7 +439,8 @@ export default function EditStoryPage() {
<Input
label="Volume/Part (optional)"
type="number"
min="1"
min="0.1"
step="any"
value={formData.volume}
onChange={handleInputChange('volume')}
placeholder="Enter volume/part number"