Compare commits

...

5 Commits

9 changed files with 126 additions and 13 deletions

View File

@@ -263,6 +263,7 @@ export const i18nData = {
"Optional": "可选", "Optional": "可选",
"Download": "下载", "Download": "下载",
"Notifications": "通知", "Notifications": "通知",
"Release Date": "发售日期",
}, },
}, },
"zh-TW": { "zh-TW": {
@@ -529,6 +530,7 @@ export const i18nData = {
"Optional": "可選", "Optional": "可選",
"Download": "下載", "Download": "下載",
"Notifications": "通知", "Notifications": "通知",
"Release Date": "發售日期",
}, },
}, },
}; };

View File

@@ -44,6 +44,7 @@ export interface CreateResourceParams {
title: string; title: string;
alternative_titles: string[]; alternative_titles: string[];
links: RLink[]; links: RLink[];
release_date?: string;
tags: number[]; tags: number[];
article: string; article: string;
images: number[]; images: number[];
@@ -77,6 +78,7 @@ export interface Resource {
id: number; id: number;
title: string; title: string;
created_at: string; created_at: string;
release_date?: string;
tags: Tag[]; tags: Tag[];
image?: Image; image?: Image;
author: User; author: User;
@@ -89,6 +91,7 @@ export interface ResourceDetails {
links: RLink[]; links: RLink[];
article: string; article: string;
createdAt: string; createdAt: string;
releaseDate?: string;
tags: Tag[]; tags: Tag[];
images: Image[]; images: Image[];
files: RFile[]; files: RFile[];

View File

@@ -25,6 +25,7 @@ import CharacterEditer, { FetchVndbCharactersButton } from "../components/charac
export default function EditResourcePage() { export default function EditResourcePage() {
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
const [altTitles, setAltTitles] = useState<string[]>([]); const [altTitles, setAltTitles] = useState<string[]>([]);
const [releaseDate, setReleaseDate] = useState<string | undefined>(undefined);
const [tags, setTags] = useState<Tag[]>([]); const [tags, setTags] = useState<Tag[]>([]);
const [article, setArticle] = useState<string>(""); const [article, setArticle] = useState<string>("");
const [images, setImages] = useState<number[]>([]); const [images, setImages] = useState<number[]>([]);
@@ -61,6 +62,7 @@ export default function EditResourcePage() {
setLinks(data.links ?? []); setLinks(data.links ?? []);
setGalleryImages(data.gallery ?? []); setGalleryImages(data.gallery ?? []);
setGalleryNsfw(data.galleryNsfw ?? []); setGalleryNsfw(data.galleryNsfw ?? []);
setReleaseDate(data.releaseDate ?? undefined);
setCharacters(data.characters ?? []); setCharacters(data.characters ?? []);
setLoading(false); setLoading(false);
} else { } else {
@@ -108,6 +110,7 @@ export default function EditResourcePage() {
gallery: galleryImages, gallery: galleryImages,
gallery_nsfw: galleryNsfw, gallery_nsfw: galleryNsfw,
characters: characters, characters: characters,
release_date: releaseDate,
}); });
if (res.success) { if (res.success) {
setSubmitting(false); setSubmitting(false);
@@ -194,6 +197,14 @@ export default function EditResourcePage() {
{t("Add Alternative Title")} {t("Add Alternative Title")}
</button> </button>
<div className={"h-2"}></div> <div className={"h-2"}></div>
<p className={"my-1"}>{t("Release Date")}</p>
<input
type="date"
className="input"
value={releaseDate || ""}
onChange={(e) => setReleaseDate(e.target.value || undefined)}
/>
<div className={"h-4"}></div>
<p className={"my-1"}>{t("Links")}</p> <p className={"my-1"}>{t("Links")}</p>
<div className={"flex flex-col"}> <div className={"flex flex-col"}>
{links.map((link, index) => { {links.map((link, index) => {

View File

@@ -24,6 +24,7 @@ import CharacterEditer, { FetchVndbCharactersButton } from "../components/charac
export default function PublishPage() { export default function PublishPage() {
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
const [altTitles, setAltTitles] = useState<string[]>([]); const [altTitles, setAltTitles] = useState<string[]>([]);
const [releaseDate, setReleaseDate] = useState<string | undefined>(undefined);
const [tags, setTags] = useState<Tag[]>([]); const [tags, setTags] = useState<Tag[]>([]);
const [article, setArticle] = useState<string>(""); const [article, setArticle] = useState<string>("");
const [images, setImages] = useState<number[]>([]); const [images, setImages] = useState<number[]>([]);
@@ -43,9 +44,14 @@ export default function PublishPage() {
const data = JSON.parse(oldData); const data = JSON.parse(oldData);
setTitle(data.title || ""); setTitle(data.title || "");
setAltTitles(data.alternative_titles || []); setAltTitles(data.alternative_titles || []);
setReleaseDate(data.release_date || undefined);
setTags(data.tags || []); setTags(data.tags || []);
setArticle(data.article || ""); setArticle(data.article || "");
setImages(data.images || []); setImages(data.images || []);
setLinks(data.links || []);
setGalleryImages(data.gallery || []);
setGalleryNsfw(data.gallery_nsfw || []);
setCharacters(data.characters || []);
} catch (e) { } catch (e) {
console.error("Failed to parse publish_data from localStorage", e); console.error("Failed to parse publish_data from localStorage", e);
} }
@@ -58,11 +64,16 @@ export default function PublishPage() {
tags: tags, tags: tags,
article: article, article: article,
images: images, images: images,
links: links,
gallery: galleryImages,
gallery_nsfw: galleryNsfw,
characters: characters,
release_date: releaseDate,
}; };
const dataString = JSON.stringify(data); const dataString = JSON.stringify(data);
localStorage.setItem("publish_data", dataString); localStorage.setItem("publish_data", dataString);
} }
}, [altTitles, article, images, tags, title]); }, [altTitles, article, images, tags, title, links, galleryImages, galleryNsfw, characters, releaseDate]);
const navigate = useNavigate(); const navigate = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
@@ -105,6 +116,7 @@ export default function PublishPage() {
const res = await network.createResource({ const res = await network.createResource({
title: title, title: title,
alternative_titles: altTitles, alternative_titles: altTitles,
release_date: releaseDate,
tags: tags.map((tag) => tag.id), tags: tags.map((tag) => tag.id),
article: article, article: article,
images: images, images: images,
@@ -201,6 +213,14 @@ export default function PublishPage() {
{t("Add Alternative Title")} {t("Add Alternative Title")}
</button> </button>
<div className={"h-2"}></div> <div className={"h-2"}></div>
<p className={"my-1"}>{t("Release Date")}</p>
<input
type="date"
className="input"
value={releaseDate || ""}
onChange={(e) => setReleaseDate(e.target.value || undefined)}
/>
<div className={"h-4"}></div>
<p className={"my-1"}>{t("Links")}</p> <p className={"my-1"}>{t("Links")}</p>
<div className={"flex flex-col"}> <div className={"flex flex-col"}>
{links.map((link, index) => { {links.map((link, index) => {

View File

@@ -209,6 +209,14 @@ export default function ResourcePage() {
</h2> </h2>
); );
})} })}
{
resource.releaseDate ? (
<div className={"px-4 py-1 text-sm text-gray-600 dark:text-gray-400 flex items-center"}>
<MdOutlineAccessTime size={18} className={"inline-block mr-1"} />
{t("Release Date")}: {resource.releaseDate.split("T")[0]}
</div>
) : null
}
<button <button
onClick={() => { onClick={() => {
navigate( navigate(

View File

@@ -1,7 +1,10 @@
package api package api
import ( import (
"log/slog"
"nysoure/server/dao"
"nysoure/server/middleware" "nysoure/server/middleware"
"time"
"nysoure/server/search" "nysoure/server/search"
@@ -20,10 +23,40 @@ func rebuildSearchIndex(c fiber.Ctx) error {
}) })
} }
func updateResourceReleaseDate(c fiber.Ctx) error {
type Request struct {
ResourceID uint `json:"resource_id"`
ReleaseDate string `json:"release_date"`
}
var req Request
if err := c.Bind().JSON(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request body: " + err.Error(),
})
}
date, err := time.Parse("2006-01-02", req.ReleaseDate)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid date format: " + err.Error(),
})
}
err = dao.UpdateResourceReleaseDate(req.ResourceID, date)
if err != nil {
slog.Error("Failed to update release date", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to update release date",
})
}
return c.JSON(fiber.Map{
"message": "Release date updated successfully",
})
}
func AddDevAPI(router fiber.Router) { func AddDevAPI(router fiber.Router) {
devGroup := router.Group("/dev") devGroup := router.Group("/dev")
devGroup.Use(middleware.DevMiddleware()) devGroup.Use(middleware.DevMiddleware())
{ {
devGroup.Post("/rebuild_search_index", rebuildSearchIndex) devGroup.Post("/rebuild_search_index", rebuildSearchIndex)
devGroup.Post("/update_resource_release_date", updateResourceReleaseDate)
} }
} }

View File

@@ -704,3 +704,14 @@ func GetResourceOwnerID(resourceID uint) (uint, error) {
} }
return uid, nil return uid, nil
} }
func UpdateResourceReleaseDate(resourceID uint, releaseDate time.Time) error {
result := db.Model(&model.Resource{}).Where("id = ?", resourceID).Update("release_date", releaseDate)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return model.NewNotFoundError("Resource not found")
}
return nil
}

View File

@@ -11,6 +11,7 @@ type Resource struct {
Title string Title string
AlternativeTitles []string `gorm:"serializer:json"` AlternativeTitles []string `gorm:"serializer:json"`
Links []Link `gorm:"serializer:json"` Links []Link `gorm:"serializer:json"`
ReleaseDate *time.Time
Article string Article string
Images []Image `gorm:"many2many:resource_images;"` Images []Image `gorm:"many2many:resource_images;"`
Tags []Tag `gorm:"many2many:resource_tags;"` Tags []Tag `gorm:"many2many:resource_tags;"`
@@ -32,12 +33,13 @@ type Link struct {
} }
type ResourceView struct { type ResourceView struct {
ID uint `json:"id"` ID uint `json:"id"`
Title string `json:"title"` Title string `json:"title"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
Tags []TagView `json:"tags"` ReleaseDate *time.Time `json:"release_date,omitempty"`
Image *ImageView `json:"image"` Tags []TagView `json:"tags"`
Author UserView `json:"author"` Image *ImageView `json:"image"`
Author UserView `json:"author"`
} }
type ResourceDetailView struct { type ResourceDetailView struct {
@@ -47,6 +49,7 @@ type ResourceDetailView struct {
Links []Link `json:"links"` Links []Link `json:"links"`
Article string `json:"article"` Article string `json:"article"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
ReleaseDate *time.Time `json:"releaseDate,omitempty"`
Tags []TagView `json:"tags"` Tags []TagView `json:"tags"`
Images []ImageView `json:"images"` Images []ImageView `json:"images"`
Files []FileView `json:"files"` Files []FileView `json:"files"`
@@ -81,12 +84,13 @@ func (r *Resource) ToView() ResourceView {
} }
return ResourceView{ return ResourceView{
ID: r.ID, ID: r.ID,
Title: r.Title, Title: r.Title,
CreatedAt: r.CreatedAt, CreatedAt: r.CreatedAt,
Tags: tags, ReleaseDate: r.ReleaseDate,
Image: image, Tags: tags,
Author: r.User.ToView(), Image: image,
Author: r.User.ToView(),
} }
} }
@@ -115,6 +119,7 @@ func (r *Resource) ToDetailView() ResourceDetailView {
Links: r.Links, Links: r.Links,
Article: r.Article, Article: r.Article,
CreatedAt: r.CreatedAt, CreatedAt: r.CreatedAt,
ReleaseDate: r.ReleaseDate,
Tags: tags, Tags: tags,
Images: images, Images: images,
Files: files, Files: files,

View File

@@ -30,6 +30,7 @@ type ResourceParams struct {
Title string `json:"title" binding:"required"` Title string `json:"title" binding:"required"`
AlternativeTitles []string `json:"alternative_titles"` AlternativeTitles []string `json:"alternative_titles"`
Links []model.Link `json:"links"` Links []model.Link `json:"links"`
ReleaseDate string `json:"release_date"`
Tags []uint `json:"tags"` Tags []uint `json:"tags"`
Article string `json:"article"` Article string `json:"article"`
Images []uint `json:"images"` Images []uint `json:"images"`
@@ -101,11 +102,20 @@ func CreateResource(uid uint, params *ResourceParams) (uint, error) {
ImageID: imageID, ImageID: imageID,
} }
} }
var date *time.Time
if params.ReleaseDate != "" {
parsedDate, err := time.Parse("2006-01-02", params.ReleaseDate)
if err != nil {
return 0, model.NewRequestError("Invalid release date format, expected YYYY-MM-DD")
}
date = &parsedDate
}
r := model.Resource{ r := model.Resource{
Title: params.Title, Title: params.Title,
AlternativeTitles: params.AlternativeTitles, AlternativeTitles: params.AlternativeTitles,
Article: params.Article, Article: params.Article,
Links: params.Links, Links: params.Links,
ReleaseDate: date,
Images: images, Images: images,
Tags: tags, Tags: tags,
UserID: uid, UserID: uid,
@@ -529,10 +539,20 @@ func UpdateResource(uid, rid uint, params *ResourceParams) error {
} }
} }
var date *time.Time
if params.ReleaseDate != "" {
parsedDate, err := time.Parse("2006-01-02", params.ReleaseDate)
if err != nil {
return model.NewRequestError("Invalid release date format, expected YYYY-MM-DD")
}
date = &parsedDate
}
r.Title = params.Title r.Title = params.Title
r.AlternativeTitles = params.AlternativeTitles r.AlternativeTitles = params.AlternativeTitles
r.Article = params.Article r.Article = params.Article
r.Links = params.Links r.Links = params.Links
r.ReleaseDate = date
r.Gallery = gallery r.Gallery = gallery
r.GalleryNsfw = nsfw r.GalleryNsfw = nsfw
r.Characters = characters r.Characters = characters