diff --git a/doc/spacemedia.notation b/doc/spacemedia.notation
index f894f5f3..87d9a66e 100644
--- a/doc/spacemedia.notation
+++ b/doc/spacemedia.notation
@@ -1,1131 +1,1135 @@
+
+
+
+
diff --git a/doc/spacemedia.uml b/doc/spacemedia.uml
index ec270542..0f9605d5 100644
--- a/doc/spacemedia.uml
+++ b/doc/spacemedia.uml
@@ -1,288 +1,289 @@
+
-
+
diff --git a/doc/spacemedia_fr_FR.properties b/doc/spacemedia_fr_FR.properties
index b5aff7ff..11ae39c6 100644
--- a/doc/spacemedia_fr_FR.properties
+++ b/doc/spacemedia_fr_FR.properties
@@ -1 +1 @@
-#Wed May 19 01:27:08 CEST 2021
+#Thu May 20 23:49:50 CEST 2021
diff --git a/sm-backend/pom.xml b/sm-backend/pom.xml
index 359bd2e5..7cb535b6 100644
--- a/sm-backend/pom.xml
+++ b/sm-backend/pom.xml
@@ -1,12 +1,35 @@
4.0.0
org.wikimedia.commons.donvip
spacemedia
0.5.0-SNAPSHOT
sm-backend
Backend application
+
+
+
+ org.wikimedia.commons.donvip
+ sm-data
+ ${project.version}
+
+
+ org.wikimedia.commons.donvip
+ sm-commons-data
+ ${project.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.mariadb.jdbc
+ mariadb-java-client
+
+
+
diff --git a/sm-backend/src/main/java/org/wikimedia/commons/donvip/spacemedia/backend/BackendApplication.java b/sm-backend/src/main/java/org/wikimedia/commons/donvip/spacemedia/backend/BackendApplication.java
new file mode 100644
index 00000000..54f1adef
--- /dev/null
+++ b/sm-backend/src/main/java/org/wikimedia/commons/donvip/spacemedia/backend/BackendApplication.java
@@ -0,0 +1,13 @@
+package org.wikimedia.commons.donvip.spacemedia.backend;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Import;
+
+@SpringBootApplication
+@Import(BackendConfiguration.class)
+public class BackendApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(BackendApplication.class, args);
+ }
+}
diff --git a/sm-backend/src/main/java/org/wikimedia/commons/donvip/spacemedia/backend/BackendConfiguration.java b/sm-backend/src/main/java/org/wikimedia/commons/donvip/spacemedia/backend/BackendConfiguration.java
new file mode 100644
index 00000000..efed19b2
--- /dev/null
+++ b/sm-backend/src/main/java/org/wikimedia/commons/donvip/spacemedia/backend/BackendConfiguration.java
@@ -0,0 +1,12 @@
+package org.wikimedia.commons.donvip.spacemedia.backend;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.wikimedia.commons.donvip.spacemedia.commons.data.CommonsDbConfiguration;
+import org.wikimedia.commons.donvip.spacemedia.data.DomainDbConfiguration;
+
+@Configuration
+@Import({CommonsDbConfiguration.class, DomainDbConfiguration.class})
+public class BackendConfiguration {
+
+}
diff --git a/sm-backend/src/main/resources/application-dev.properties b/sm-backend/src/main/resources/application-dev.properties
new file mode 100644
index 00000000..0be2dfb1
--- /dev/null
+++ b/sm-backend/src/main/resources/application-dev.properties
@@ -0,0 +1,5 @@
+#spring.jpa.open-in-view=true
+#spring.jpa.show-sql=true
+#spring.jpa.properties.hibernate.format_sql=true
+#logging.level.org.hibernate.SQL=DEBUG
+#logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
diff --git a/sm-backend/src/main/resources/application-toolforge.properties b/sm-backend/src/main/resources/application-toolforge.properties
new file mode 100644
index 00000000..e956ed47
--- /dev/null
+++ b/sm-backend/src/main/resources/application-toolforge.properties
@@ -0,0 +1,12 @@
+# see https://wikitech.wikimedia.org/wiki/Help:Toolforge/Database#User_databases
+domain.datasource.url=
+domain.datasource.username=
+domain.datasource.password=
+
+commons.datasource.url=jdbc:mariadb://commonswiki.analytics.db.svc.eqiad.wmflabs:3306/commonswiki_p
+commons.datasource.username=
+commons.datasource.password=
+
+#server.servlet.contextPath=/spacemedia
+server.port=8000
+logging.config=classpath:logback-spring-toolforge.xml
diff --git a/sm-backend/src/main/resources/application.properties b/sm-backend/src/main/resources/application.properties
new file mode 100644
index 00000000..970f9098
--- /dev/null
+++ b/sm-backend/src/main/resources/application.properties
@@ -0,0 +1,38 @@
+# Spring
+
+spring.jackson.default-property-inclusion=non_empty
+spring.jackson.property-naming-strategy=SNAKE_CASE
+
+spring.jpa.hibernate.ddl-auto=update
+spring.jpa.open-in-view=false
+spring.jpa.show-sql=false
+spring.jpa.properties.hibernate.format_sql=false
+spring.jpa.properties.hibernate.search.default.directory_provider = filesystem
+spring.jpa.properties.hibernate.search.default.indexBase = /data/project/spacemedia/index/default
+
+# Local database for development
+domain.datasource.url=jdbc:mariadb://localhost:3306/root_spacemedia
+domain.datasource.username=root
+domain.datasource.password=
+domain.datasource.driver-class-name=org.mariadb.jdbc.Driver
+domain.datasource.hikari.maximum-pool-size=4
+domain.datasource.hikari.max-lifetime=300000
+domain.datasource.hikari.connectionInitSql = SET NAMES 'utf8mb4'
+
+commons.datasource.url=jdbc:mariadb://localhost:3307/commonswiki_p
+commons.datasource.username=
+commons.datasource.password=
+commons.datasource.driver-class-name=org.mariadb.jdbc.Driver
+commons.datasource.hikari.maximum-pool-size=4
+commons.datasource.hikari.max-lifetime=300000
+commons.datasource.hikari.connectionInitSql = SET NAMES 'utf8mb4'
+
+commons.api.url = https://commons.wikimedia.org/w/api.php
+commons.api.rest.url = https://commons.wikimedia.org/w/rest.php
+commons.api.account = OptimusPrimeBot
+commons.api.oauth1.consumer-token =
+commons.api.oauth1.consumer-secret =
+commons.api.oauth1.access-token =
+commons.api.oauth1.access-secret =
+commons.cat.search.depth = 1
+commons.img.preview.width = 640
diff --git a/sm-backend/src/main/resources/banner.txt b/sm-backend/src/main/resources/banner.txt
new file mode 100644
index 00000000..d5bc6fe0
--- /dev/null
+++ b/sm-backend/src/main/resources/banner.txt
@@ -0,0 +1,6 @@
+ _______..______ ___ ______ _______ .___ ___. _______ _______ __ ___
+ / || _ \ / \ / || ____|| \/ | | ____|| \ | | / \
+ | (----`| |_) | / ^ \ | ,----'| |__ | \ / | | |__ | .--. || | / ^ \
+ \ \ | ___/ / /_\ \ | | | __| | |\/| | | __| | | | || | / /_\ \
+.----) | | | / _____ \ | `----.| |____ | | | | | |____ | '--' || | / _____ \
+|_______/ | _| /__/ \__\ \______||_______||__| |__| |_______||_______/ |__|/__/ \__\
diff --git a/sm-backend/src/main/resources/logback-spring-toolforge.xml b/sm-backend/src/main/resources/logback-spring-toolforge.xml
new file mode 100644
index 00000000..df02b423
--- /dev/null
+++ b/sm-backend/src/main/resources/logback-spring-toolforge.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/sm-data/pom.xml b/sm-data/pom.xml
index 58bd0f44..f0c3529c 100644
--- a/sm-data/pom.xml
+++ b/sm-data/pom.xml
@@ -1,12 +1,21 @@
4.0.0
org.wikimedia.commons.donvip
spacemedia
0.5.0-SNAPSHOT
sm-data
- Data classes
+ JPA data classes
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
diff --git a/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/DomainDbConfiguration.java b/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/DomainDbConfiguration.java
new file mode 100644
index 00000000..83b6e871
--- /dev/null
+++ b/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/DomainDbConfiguration.java
@@ -0,0 +1,64 @@
+package org.wikimedia.commons.donvip.spacemedia.data;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.persistence.EntityManagerFactory;
+import javax.sql.DataSource;
+
+import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
+import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy;
+import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.env.Environment;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+@Configuration
+@EnableTransactionManagement
+@EnableJpaRepositories(
+ entityManagerFactoryRef = "domainEntityManagerFactory",
+ transactionManagerRef = "domainTransactionManager",
+ basePackageClasses = {DomainDbConfiguration.class})
+public class DomainDbConfiguration {
+
+ @Primary
+ @Bean(name = "domainDataSourceProperties")
+ @ConfigurationProperties("domain.datasource")
+ public DataSourceProperties dataSourceProperties() {
+ return new DataSourceProperties();
+ }
+
+ @Primary
+ @Bean(name = "domainDataSource")
+ @ConfigurationProperties("domain.datasource.hikari")
+ public DataSource dataSource() {
+ return dataSourceProperties().initializeDataSourceBuilder().build();
+ }
+
+ @Primary
+ @Bean(name = "domainEntityManagerFactory")
+ public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, Environment env) {
+ Map hibernateProperties = new HashMap<>();
+ hibernateProperties.put("hibernate.physical_naming_strategy", new SpringPhysicalNamingStrategy());
+ hibernateProperties.put("hibernate.implicit_naming_strategy", new SpringImplicitNamingStrategy());
+ hibernateProperties.put("hibernate.hbm2ddl.auto", env.getProperty("spring.jpa.hibernate.ddl-auto"));
+ return builder.dataSource(dataSource())
+ .packages(getClass().getPackage().getName())
+ .properties(hibernateProperties)
+ .persistenceUnit("domain").build();
+ }
+
+ @Primary
+ @Bean(name = "domainTransactionManager")
+ public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
+ return new JpaTransactionManager(entityManagerFactory);
+ }
+}
diff --git a/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/Human.java b/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/Human.java
new file mode 100644
index 00000000..4a7a47b4
--- /dev/null
+++ b/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/Human.java
@@ -0,0 +1,8 @@
+package org.wikimedia.commons.donvip.spacemedia.data;
+
+import javax.persistence.Entity;
+
+@Entity
+public class Human extends Person {
+
+}
diff --git a/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/HumanRepository.java b/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/HumanRepository.java
new file mode 100644
index 00000000..668011e1
--- /dev/null
+++ b/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/HumanRepository.java
@@ -0,0 +1,7 @@
+package org.wikimedia.commons.donvip.spacemedia.data;
+
+import org.springframework.data.repository.CrudRepository;
+
+public interface HumanRepository extends CrudRepository {
+
+}
diff --git a/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/Organization.java b/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/Organization.java
new file mode 100644
index 00000000..b0e93426
--- /dev/null
+++ b/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/Organization.java
@@ -0,0 +1,40 @@
+package org.wikimedia.commons.donvip.spacemedia.data;
+
+import java.net.URL;
+import java.util.Objects;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+
+@Entity
+public class Organization extends Person {
+
+ /**
+ * Organization website URL.
+ */
+ @Column(nullable = true)
+ private URL website;
+
+ public URL getWebsite() {
+ return website;
+ }
+
+ public void setWebsite(URL website) {
+ this.website = website;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * super.hashCode() + Objects.hash(website);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj) || getClass() != obj.getClass())
+ return false;
+ Organization other = (Organization) obj;
+ return Objects.equals(website, other.website);
+ }
+}
diff --git a/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/OrganizationRepository.java b/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/OrganizationRepository.java
new file mode 100644
index 00000000..6b0b8d7f
--- /dev/null
+++ b/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/OrganizationRepository.java
@@ -0,0 +1,7 @@
+package org.wikimedia.commons.donvip.spacemedia.data;
+
+import org.springframework.data.repository.CrudRepository;
+
+public interface OrganizationRepository extends CrudRepository {
+
+}
diff --git a/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/Person.java b/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/Person.java
new file mode 100644
index 00000000..06e10559
--- /dev/null
+++ b/sm-data/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/Person.java
@@ -0,0 +1,58 @@
+package org.wikimedia.commons.donvip.spacemedia.data;
+
+import java.util.Objects;
+
+import javax.persistence.Column;
+import javax.persistence.Id;
+import javax.persistence.MappedSuperclass;
+
+/**
+ * Abstract superclass of persons (organizations and humans).
+ */
+@MappedSuperclass
+public abstract class Person {
+
+ /**
+ * Unique name identifying the person.
+ */
+ @Id
+ @Column(nullable = false)
+ protected String name;
+
+ /**
+ * Optional Wikidata Q identifier (should be defined for most organizations, but few humans)
+ */
+ @Column(nullable = true)
+ protected String wikidata;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getWikidata() {
+ return wikidata;
+ }
+
+ public void setWikidata(String wikidata) {
+ this.wikidata = wikidata;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, wikidata);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null || getClass() != obj.getClass())
+ return false;
+ Person other = (Person) obj;
+ return Objects.equals(name, other.name) && Objects.equals(wikidata, other.wikidata);
+ }
+}