under construction
This commit is contained in:
1
sitemap/README.md
Normal file
1
sitemap/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# Sitemap & Syndication
|
||||
34
sitemap/build.gradle.kts
Normal file
34
sitemap/build.gradle.kts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Project Asgard
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("elex-springboot")
|
||||
|
||||
id("org.springframework.boot") version "2.5.3"
|
||||
id("io.spring.dependency-management") version "1.0.11.RELEASE"
|
||||
}
|
||||
|
||||
version = "1.0.0"
|
||||
description = "Sitemap builder"
|
||||
|
||||
tasks.bootJar{
|
||||
enabled = false
|
||||
}
|
||||
tasks.jar {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.github.spullara.mustache.java:compiler:0.9.10")
|
||||
|
||||
implementation("org.springframework.boot:spring-boot-starter")
|
||||
|
||||
compileOnly("org.projectlombok:lombok")
|
||||
annotationProcessor("org.projectlombok:lombok")
|
||||
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Project Asgard
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package com.elex_project.asgard.sitemap;
|
||||
|
||||
/*import com.samskivert.mustache.Mustache;
|
||||
import com.samskivert.mustache.Template;*/
|
||||
|
||||
import com.github.mustachejava.DefaultMustacheFactory;
|
||||
import com.github.mustachejava.Mustache;
|
||||
import com.github.mustachejava.MustacheFactory;
|
||||
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* @link https://www.sitemaps.org/ko/protocol.html
|
||||
*/
|
||||
public abstract class BaseSitemapService {
|
||||
private final Mustache template;
|
||||
|
||||
protected BaseSitemapService() {
|
||||
MustacheFactory factory = new DefaultMustacheFactory();
|
||||
template = factory.compile("sitemap/sitemap.mustache");
|
||||
}
|
||||
|
||||
protected abstract Sitemap sitemap();
|
||||
|
||||
public String getSitemap() {
|
||||
StringWriter writer = new StringWriter();
|
||||
template.execute(writer, sitemap());
|
||||
return writer.toString()
|
||||
.replaceAll("[\n\t]", "")
|
||||
.replaceAll("\\s{2,}", " ");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Project Asgard
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package com.elex_project.asgard.sitemap;
|
||||
|
||||
public enum Frequency {
|
||||
ALWAYS("always"),
|
||||
HOURLY("hourly"),
|
||||
DAILY("daily"),
|
||||
WEEKLY("weekly"),
|
||||
MONTHLY("monthly"),
|
||||
YEARLY("yearly"),
|
||||
NEVER("never");
|
||||
|
||||
private final String value;
|
||||
|
||||
Frequency(final String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Project Asgard
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package com.elex_project.asgard.sitemap;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class Sitemap {
|
||||
private final List<SitemapItem> items;
|
||||
|
||||
public Sitemap() {
|
||||
items = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void addItem(final SitemapItem item) {
|
||||
this.items.add(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Project Asgard
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package com.elex_project.asgard.sitemap;
|
||||
|
||||
import com.elex_project.asgard.syndication.Formatter;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Slf4j
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class SitemapItem {
|
||||
private static final ZoneId defaultZone = ZoneOffset.ofHours(0).normalized();
|
||||
|
||||
private String loc;
|
||||
private LocalDateTime lastMod;
|
||||
private Frequency changeFreq;
|
||||
private Float priority;
|
||||
|
||||
public String getLastModIso(){
|
||||
return Formatter.formatIso(lastMod);
|
||||
/*return lastMod.atZone(ZoneId.systemDefault())
|
||||
.format(DateTimeFormatter.ISO_ZONED_DATE_TIME.withZone(defaultZone));*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Project Asgard
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package com.elex_project.asgard.sitemap;
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Project Asgard
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package com.elex_project.asgard.syndication;
|
||||
|
||||
import com.github.mustachejava.DefaultMustacheFactory;
|
||||
import com.github.mustachejava.Mustache;
|
||||
import com.github.mustachejava.MustacheFactory;
|
||||
|
||||
import java.io.StringWriter;
|
||||
|
||||
//@Service
|
||||
public abstract class BaseSyndicationService {
|
||||
private final Mustache templateRss, templateAtom;
|
||||
|
||||
public BaseSyndicationService(){
|
||||
MustacheFactory factory = new DefaultMustacheFactory();
|
||||
templateRss = factory.compile("syndication/rss2.mustache");
|
||||
templateAtom = factory.compile("syndication/atom.mustache");
|
||||
}
|
||||
|
||||
protected abstract Syndication syndication();
|
||||
|
||||
public String getRssSyndication() {
|
||||
StringWriter writer = new StringWriter();
|
||||
templateRss.execute(writer, syndication());
|
||||
return writer.toString()
|
||||
.replaceAll("[\n\t]","")
|
||||
.replaceAll("\\s{2,}", " ");
|
||||
}
|
||||
|
||||
public String getAtomSyndication() {
|
||||
StringWriter writer = new StringWriter();
|
||||
templateAtom.execute(writer, syndication());
|
||||
return writer.toString()
|
||||
.replaceAll("[\n\t]","")
|
||||
.replaceAll("\\s{2,}", " ");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Project Asgard
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package com.elex_project.asgard.syndication;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public interface Formatter {
|
||||
|
||||
static final ZoneId defaultZone = ZoneOffset.ofHours(0).normalized();
|
||||
|
||||
static String formatIso(final LocalDateTime time){
|
||||
return time.atZone(ZoneId.systemDefault())
|
||||
.format(DateTimeFormatter.ISO_ZONED_DATE_TIME.withZone(defaultZone));
|
||||
}
|
||||
|
||||
static String formatRfc(final LocalDateTime time){
|
||||
return time.atZone(ZoneOffset.systemDefault())
|
||||
.format(DateTimeFormatter.RFC_1123_DATE_TIME.withZone(defaultZone));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Project Asgard
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package com.elex_project.asgard.syndication;
|
||||
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@Slf4j
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder @ToString
|
||||
public class Syndication {
|
||||
private String title;
|
||||
private String link, linkRss, linkAtom;
|
||||
private String description;
|
||||
private String copyright;
|
||||
private String imageUrl;
|
||||
@Builder.Default
|
||||
private Locale language = Locale.getDefault();
|
||||
@Builder.Default
|
||||
private LocalDateTime lastBuildDate = LocalDateTime.now();
|
||||
@Singular
|
||||
protected List<SyndicationItem> items;
|
||||
|
||||
public void addItem(final SyndicationItem item) {
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
public final String getLastBuildDateIso() {
|
||||
return Formatter.formatIso(lastBuildDate);
|
||||
}
|
||||
|
||||
public final String getLastBuildDateRfc() {
|
||||
return Formatter.formatRfc(lastBuildDate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Project Asgard
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package com.elex_project.asgard.syndication;
|
||||
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Slf4j
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder @ToString
|
||||
public class SyndicationItem {
|
||||
private String title;
|
||||
private String url;
|
||||
private String description;
|
||||
private String authorName, authorEmail;
|
||||
private LocalDateTime pubDate;
|
||||
private LocalDateTime modDate;
|
||||
|
||||
public final String getPubDateIsoString(){
|
||||
return Formatter.formatIso(pubDate);
|
||||
}
|
||||
public final String getModDateIsoString(){
|
||||
return Formatter.formatIso(modDate);
|
||||
}
|
||||
|
||||
public final String getPubDateRfcString(){
|
||||
return Formatter.formatRfc(pubDate);
|
||||
}
|
||||
public final String getModDateRfcString(){
|
||||
return Formatter.formatRfc(modDate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Project Asgard
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package com.elex_project.asgard.syndication;
|
||||
11
sitemap/src/main/resources/sitemap/sitemap.mustache
Normal file
11
sitemap/src/main/resources/sitemap/sitemap.mustache
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
{{#items}}
|
||||
<url>
|
||||
<loc>{{loc}}</loc>
|
||||
{{#lastMod}}<lastmod>{{lastModIso}}</lastmod>{{/lastMod}}
|
||||
{{#changeFreq}}<changefreq>{{changeFreq}}</changefreq>{{/changeFreq}}
|
||||
{{#priority}}<priority>{{priority}}</priority>{{/priority}}
|
||||
</url>
|
||||
{{/items}}
|
||||
</urlset>
|
||||
23
sitemap/src/main/resources/syndication/atom.mustache
Normal file
23
sitemap/src/main/resources/syndication/atom.mustache
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<id>https://www.elex-project.com/</id>
|
||||
<title>{{title}}</title>
|
||||
<link rel="self" thref="{{linkAtom}}" />
|
||||
<updated>{{lastBuildDateIso}}</updated>
|
||||
<rights>{{copyright}}</rights>
|
||||
<logo>{{imageUrl}}</logo>
|
||||
{{#items}}
|
||||
<entry>
|
||||
<id>{{url}}</id>
|
||||
<title>{{title}}</title>
|
||||
<link rel="alternate" href="{{url}}" />
|
||||
<updated>{{modDateIso}}</updated>
|
||||
<published>{{pubDateIso}}</published>
|
||||
<summary>{{description}}</summary>
|
||||
<author>
|
||||
<name>{{author}}</name>
|
||||
<email>{{email}}</email>
|
||||
</author>
|
||||
</entry>
|
||||
{{/items}}
|
||||
</feed>
|
||||
28
sitemap/src/main/resources/syndication/rss2.mustache
Normal file
28
sitemap/src/main/resources/syndication/rss2.mustache
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>{{title}}</title>
|
||||
<link>{{link}}</link>
|
||||
<atom:link href="{{linkRss}}" rel="self" type="application/rss+xml" />
|
||||
<atom:link href="{{linkAtom}}" rel="alternate" type="application/atom+xml" />
|
||||
<description>{{description}}</description>
|
||||
<language>{{language}}</language>
|
||||
<copyright>{{copyright}}</copyright>
|
||||
<lastBuildDate>{{lastBuildDateRfc}}</lastBuildDate>
|
||||
<image>
|
||||
<url>{{imageUrl}}</url>
|
||||
<title>{{title}}</title>
|
||||
<link>{{link}}</link>
|
||||
</image>
|
||||
{{#items}}
|
||||
<item>
|
||||
<title>{{title}}</title>
|
||||
<link>{{url}}</link>
|
||||
<guid>{{url}}</guid>
|
||||
<description>{{description}}</description>
|
||||
<author>{{authorEmail}} ({{authorName}})</author>
|
||||
<pubDate>{{pubDateRfc}}</pubDate>
|
||||
</item>
|
||||
{{/items}}
|
||||
</channel>
|
||||
</rss>
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Project Asgard
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package com.elex_project.asgard.sitemap;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class SitemapServiceTest {
|
||||
private static BaseSitemapService service;
|
||||
|
||||
@BeforeAll
|
||||
static void prepare() {
|
||||
service = new BaseSitemapService() {
|
||||
@Override
|
||||
protected Sitemap sitemap() {
|
||||
final Sitemap sitemap = new Sitemap();
|
||||
sitemap.addItem(SitemapItem.builder()
|
||||
.loc("https://aaa.com/1")
|
||||
.lastMod(LocalDateTime.now())
|
||||
.changeFreq(Frequency.HOURLY)
|
||||
.build());
|
||||
sitemap.addItem(SitemapItem.builder()
|
||||
.loc("https://aaa.com/2")
|
||||
.lastMod(LocalDateTime.now())
|
||||
.build());
|
||||
return sitemap;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
void sitemap() {
|
||||
final String output = service.getSitemap();
|
||||
System.out.println(output);
|
||||
|
||||
assertTrue(output.contains("<loc>https://aaa.com/2</loc>"));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Project Asgard
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package com.elex_project.asgard.syndication;
|
||||
|
||||
import com.elex_project.asgard.syndication.BaseSyndicationService;
|
||||
import com.elex_project.asgard.syndication.Syndication;
|
||||
import com.elex_project.asgard.syndication.SyndicationItem;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class SyndicationServiceTest {
|
||||
|
||||
private static BaseSyndicationService service;
|
||||
|
||||
@BeforeAll
|
||||
static void prepare() {
|
||||
service = new BaseSyndicationService() {
|
||||
@Override
|
||||
protected Syndication syndication() {
|
||||
final Syndication syndication = Syndication.builder()
|
||||
.title("Elex Project")
|
||||
.link("https://www.elex-project.com/")
|
||||
.linkAtom("https://www.elex-project.com/atom")
|
||||
.linkRss("https://www.elex-project.com/rss")
|
||||
.description("Sample description")
|
||||
.language(Locale.KOREAN)
|
||||
.copyright("Copyright (c) 2021 Elex. All Rights Reserved.")
|
||||
.imageUrl("https://www.elex-project.com/images/logo.png")
|
||||
.lastBuildDate(LocalDateTime.now())
|
||||
.item(SyndicationItem.builder()
|
||||
.authorName("Elex")
|
||||
.authorEmail("email@example.com")
|
||||
.description("Sample article")
|
||||
.modDate(LocalDateTime.now())
|
||||
.pubDate(LocalDateTime.now())
|
||||
.title("Sample title")
|
||||
.url("https://something.com/1")
|
||||
.build())
|
||||
.item(SyndicationItem.builder()
|
||||
.authorName("Elex")
|
||||
.authorEmail("email@example.com")
|
||||
.description("Sample article 2")
|
||||
.modDate(LocalDateTime.now())
|
||||
.pubDate(LocalDateTime.now())
|
||||
.title("Sample title 2")
|
||||
.url("https://something.com/2")
|
||||
.build())
|
||||
.build();
|
||||
final List<SyndicationItem> items = new ArrayList<>();// todo
|
||||
items.forEach(syndication::addItem);
|
||||
return syndication;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void rss() {
|
||||
final String out = service.getRssSyndication();
|
||||
System.out.println(out);
|
||||
|
||||
assertTrue(out.contains("<link>https://something.com/2</link>"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void atom() {
|
||||
final String out = service.getAtomSyndication();
|
||||
System.out.println(out);
|
||||
|
||||
assertTrue(out.contains("<id>https://something.com/2</id>"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void convIso() {
|
||||
LocalDateTime dt = LocalDateTime.now();
|
||||
String t = dt.atZone(ZoneId.systemDefault())
|
||||
.format(DateTimeFormatter.ISO_ZONED_DATE_TIME
|
||||
.withZone(ZoneOffset.ofHours(0)));
|
||||
System.out.println(t);
|
||||
}
|
||||
|
||||
@Test
|
||||
void convRfc() {
|
||||
LocalDateTime dt = LocalDateTime.now();
|
||||
String t = dt.atZone(ZoneId.systemDefault())
|
||||
.format(DateTimeFormatter.RFC_1123_DATE_TIME
|
||||
.withZone(ZoneOffset.ofHours(0)));
|
||||
System.out.println(t);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user