GuestBook.java

/*
 * Copyright (C) 2012-2026Gerd Neugebauer
 *
 * This file is distributed under the 3-clause BSD license.
 * See file LICENSE for details.
 */
package org.ctan.site.domain.site;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

import org.apache.lucene.index.CorruptIndexException;
import org.ctan.site.domain.AbstractEntity;
import org.ctan.site.domain.account.User;
import org.ctan.site.services.search.base.IndexType;
import org.ctan.site.services.search.base.IndexingSession;
import org.ctan.site.services.search.base.IndexingSession.IndexArgs;
import org.ctan.site.services.search.base.Searchable;

import com.fasterxml.jackson.annotation.JsonFormat;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder.Default;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;

/**
 * This domain class represents an entry in the guest book.
 *
 * @author <a href="mailto:gene@ctan.org">Gerd Neugebauer</a>
 */
@Entity
@Table(name = "guestbook")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@EqualsAndHashCode(callSuper = false)
@SuppressFBWarnings(value = "EI_EXPOSE_REP")
public class GuestBook extends AbstractEntity implements Searchable {

    /**
     * The field <code>title</code> contains the title of the guest book item.
     */
    @Column(length = 128, nullable = false)
    private String title;

    /**
     * The field <code>text</code> contains the text body of the guest book
     * item.
     */
    @Column(length = 2048, nullable = false)
    private String text;

    /**
     * The field <code>name</code> contains the optional name of the author.
     */
    @Column(length = 128, nullable = true)
    private String name;

    /**
     * The field <code>email</code> contains the optional email address of the
     * guest book item.
     */
    @Column(length = 255, nullable = true)
    private String email;

    /**
     * The field <code>hideEmail</code> contains the indicator to hide the email
     * address.
     */
    @Column(name = "hide_email")
    private boolean hideEmail;

    /**
     * The field <code>hidden</code> contains the indicator to hide the complete
     * item. This is used for spurious items.
     */
    @Column
    @Default
    private boolean hidden = false;

    /**
     * The field <code>user</code> contains the optional user who has created
     * the item.
     */
    @ManyToOne
    private User user;

    /**
     * The field <code>dateCreated</code> contains the time stamp when the item
     * has been created.
     */
    @Column(name = "date_created")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
    @Default
    private LocalDateTime dateCreated = LocalDateTime.now();

    /**
     * The field <code>lastUpdated</code> contains the time stamp when the item
     * has been updated.
     */
    @Column(name = "last_updated")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
    @Default
    private LocalDateTime lastUpdated = LocalDateTime.now();

    /**
     * The field <code>parent</code> contains the reference to the parent item.
     */
    @ManyToOne
    @Default
    private GuestBook parent = null;

    // static hasMany = [ followup: Guestbook ]
    // static constraints = {
    // lastUpdated()
    // hidden()
    // title maxSize: 128
    // text blank: false, maxSize: 2048
    // name nullable: true, maxSize: 128
    // email nullable: true, maxSize: 255
    // hideEmail()
    // user nullable: true
    // parent nullable: true
    // dateCreated()
    // }

    /**
     * The method <code>getIndexType</code> provides means to name the
     * indexType.
     *
     * @return the index type
     */
    public IndexType getIndexType() {

        return IndexType.GUESTBOOK;
    }

    /**
     * {@inheritDoc}
     *
     * @see org.ctan.site.services.search.base.Searchable#indexPath()
     */
    @Override
    public String indexPath() {

        return "/guestbook/" + getId();
    }

    /**
     * The method <code>toMap</code> provides means to get the instance as an
     * immutable Map.
     *
     * @return the Map
     */
    public Map<String, Object> toMap() {

        Map<String, Object> map = new HashMap<>();
        map.put("id", getId());
        map.put("title", title);
        map.put("text", text);
        map.put("hidden", hidden);
        map.put("hideEmail", hideEmail);
        map.put("name", name);
        if (user != null) {
            if (user.getShowName()) {
                map.put("name", user.getName());
            }
            map.put("account", user.getAccount());
        }
        map.put("created", String.format("%tF", dateCreated));
        return map;
    }

    /**
     * {@inheritDoc}
     *
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {

        return text.length() < 24 ? text : text.substring(0, 24) + "...";
    }

    /**
     * This method <code>toUrl</code> returns the URL for the search index.
     *
     * @return the URL
     */
    public String toUrl() {

        return "/guestbook/item/" + getId();
    }

    /**
     * {@inheritDoc}
     *
     * @see org.ctan.site.services.search.base.Searchable#updateIndex(org.ctan.site.services.search.base.IndexingSession,
     *     java.lang.String)
     */
    @Override
    public void updateIndex(IndexingSession session, String locale)
        throws CorruptIndexException,
            IOException {

        session.updateIndex(indexPath(),
            IndexArgs.builder()
                .display(title)
                .locale(locale)
                .type(IndexType.GUESTBOOK)
                .title(title)
                .content(new String[]{
                    title,
                    text
                })
                .build());
    }
}