]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5561 New migration task at start-up to feed ES with issue_project_permissions
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 8 Sep 2014 14:18:32 +0000 (16:18 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 9 Sep 2014 09:41:03 +0000 (11:41 +0200)
16 files changed:
server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java
server/sonar-server/src/main/java/org/sonar/server/issue/db/IssueAuthorizationDao.java
server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
server/sonar-server/src/main/java/org/sonar/server/search/IndexSynchronizer.java
server/sonar-server/src/main/java/org/sonar/server/user/db/GroupDao.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/user/db/package-info.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueAuthorizationIndexMediumTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/user/db/GroupDaoTest.java [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/user/db/GroupDaoTest/empty.xml [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/user/db/GroupDaoTest/insert-result.xml [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/user/db/GroupDaoTest/select_by_key.xml [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java
sonar-core/src/main/java/org/sonar/core/user/GroupDto.java
sonar-core/src/main/java/org/sonar/core/user/GroupMapper.java [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/core/issue/db/IssueAuthorizationMapper.xml
sonar-core/src/main/resources/org/sonar/core/user/GroupMapper.xml [new file with mode: 0644]

index d9fc72df394240077f99f840650295fb9256e8f9..3cf1008ca3058729506938e2c2b5ecd6bbb56995 100644 (file)
@@ -40,6 +40,7 @@ import org.sonar.server.measure.persistence.MeasureDao;
 import org.sonar.server.measure.persistence.MetricDao;
 import org.sonar.server.qualityprofile.db.ActiveRuleDao;
 import org.sonar.server.rule.db.RuleDao;
+import org.sonar.server.user.db.GroupDao;
 
 import java.util.Map;
 
@@ -63,6 +64,7 @@ public class DbClient implements ServerComponent {
   private final ActivityDao activityDao;
   private final AuthorizationDao authorizationDao;
   private final UserDao userDao;
+  private final GroupDao groupDao;
   private final IssueDao issueDao;
   private final IssueAuthorizationDao issueAuthorizationDao;
 
@@ -87,6 +89,7 @@ public class DbClient implements ServerComponent {
     activityDao = getDao(map, ActivityDao.class);
     authorizationDao = getDao(map, AuthorizationDao.class);
     userDao = getDao(map, UserDao.class);
+    groupDao = getDao(map, GroupDao.class);
     issueDao = getDao(map, IssueDao.class);
     issueAuthorizationDao = getDao(map, IssueAuthorizationDao.class);
   }
@@ -159,6 +162,10 @@ public class DbClient implements ServerComponent {
     return userDao;
   }
 
+  public GroupDao groupDao() {
+    return groupDao;
+  }
+
   private <K> K getDao(Map<Class, DaoComponent> map, Class<K> clazz) {
     return (K) map.get(clazz);
   }
index 64074abe00db5f21ddddcf120f0fed8b585d5b16..4548ec1beeea11f3b8026161be940826124dca47 100644 (file)
@@ -63,9 +63,9 @@ public class IssueAuthorizationDao extends BaseDao<IssueAuthorizationMapper, Iss
 
     List<Map<String, String>> rows = session.selectList("org.sonar.core.issue.db.IssueAuthorizationMapper.selectAfterDate", params);
     for (Map<String, String> row : rows) {
-      String project = row.get("PROJECT");
-      String user = row.get("USER");
-      String group = row.get("PERMISSION_GROUP");
+      String project = row.get("project");
+      String user = row.get("user");
+      String group = row.get("permission_group");
       IssueAuthorizationDto issueAuthorizationDto = authorizationDtoMap.get(project);
       if (issueAuthorizationDto == null) {
         issueAuthorizationDto = new IssueAuthorizationDto()
index 153e75b83ff79b4b319163e12d6a4ba4c5d59bab..2009a7e741c9246356a1c188ec74987c0e8e2117 100644 (file)
@@ -165,6 +165,7 @@ import org.sonar.server.ui.PageDecorations;
 import org.sonar.server.ui.Views;
 import org.sonar.server.updatecenter.ws.UpdateCenterWs;
 import org.sonar.server.user.*;
+import org.sonar.server.user.db.GroupDao;
 import org.sonar.server.user.ws.FavoritesWs;
 import org.sonar.server.user.ws.UserPropertiesWs;
 import org.sonar.server.user.ws.UsersWs;
@@ -227,6 +228,7 @@ class ServerComponents {
       DbClient.class,
       MeasureFilterDao.class,
       ActivityDao.class,
+      GroupDao.class,
 
       // Elasticsearch
       SearchClient.class,
index ee9caa8174d8bc727639fb33efc3c7f205cb7595..20331459f3df7b4e67262c6adaf4ac201368fe83 100644 (file)
@@ -25,6 +25,7 @@ import org.sonar.core.persistence.DbSession;
 import org.sonar.server.activity.index.ActivityIndex;
 import org.sonar.server.db.Dao;
 import org.sonar.server.db.DbClient;
+import org.sonar.server.issue.index.IssueAuthorizationIndex;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.qualityprofile.index.ActiveRuleIndex;
 import org.sonar.server.rule.index.RuleIndex;
@@ -51,8 +52,7 @@ public class IndexSynchronizer {
     long start = System.currentTimeMillis();
     synchronize(session, db.ruleDao(), index.get(RuleIndex.class));
     synchronize(session, db.issueDao(), index.get(IssueIndex.class));
-    // TODO
-    //synchronize(session, db.issueAuthorizationDao(), index.get(IssueAuthorizationIndex.class));
+    synchronize(session, db.issueAuthorizationDao(), index.get(IssueAuthorizationIndex.class));
     synchronize(session, db.activeRuleDao(), index.get(ActiveRuleIndex.class));
     synchronize(session, db.activityDao(), index.get(ActivityIndex.class));
     session.commit();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/db/GroupDao.java b/server/sonar-server/src/main/java/org/sonar/server/user/db/GroupDao.java
new file mode 100644 (file)
index 0000000..9f6a5db
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.user.db;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.sonar.api.utils.System2;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.user.GroupDto;
+import org.sonar.core.user.GroupMapper;
+import org.sonar.server.db.BaseDao;
+
+/**
+ * @since 3.2
+ */
+public class GroupDao extends BaseDao<GroupMapper, GroupDto, String> {
+
+  public GroupDao() {
+    this(System2.INSTANCE);
+  }
+
+  @VisibleForTesting
+  public GroupDao(System2 system) {
+    super(GroupMapper.class, system);
+  }
+
+  @Override
+  protected GroupDto doGetNullableByKey(DbSession session, String key) {
+    return mapper(session).selectByKey(key);
+  }
+
+  @Override
+  protected GroupDto doInsert(DbSession session, GroupDto item) {
+    mapper(session).insert(item);
+    return item;
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/db/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/user/db/package-info.java
new file mode 100644 (file)
index 0000000..51f6154
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.user.db;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueAuthorizationIndexMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueAuthorizationIndexMediumTest.java
new file mode 100644 (file)
index 0000000..14eaa3f
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.issue.index;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.component.ComponentDto;
+import org.sonar.core.permission.PermissionFacade;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.user.GroupDto;
+import org.sonar.core.user.UserDto;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.platform.Platform;
+import org.sonar.server.tester.ServerTester;
+
+import java.util.Date;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class IssueAuthorizationIndexMediumTest {
+
+  @ClassRule
+  public static ServerTester tester = new ServerTester();
+
+  DbClient db;
+  DbSession session;
+  IssueAuthorizationIndex index;
+
+  ComponentDto project;
+
+  @Before
+  public void setUp() throws Exception {
+    tester.clearDbAndIndexes();
+    db = tester.get(DbClient.class);
+    session = db.openSession(false);
+    index = tester.get(IssueAuthorizationIndex.class);
+  }
+
+  @After
+  public void after() {
+    session.close();
+  }
+
+  @Test
+  public void synchronize() throws Exception {
+    project = new ComponentDto()
+      .setKey("Sample")
+      .setProjectId(1L);
+    db.componentDao().insert(session, project);
+
+    GroupDto sonarUsers = new GroupDto().setName("devs");
+    db.groupDao().insert(session, sonarUsers);
+
+    UserDto john = new UserDto().setLogin("john").setName("John").setActive(true);
+    db.userDao().insert(session, john);
+
+    tester.get(PermissionFacade.class).insertGroupPermission(project.getId(), "devs", UserRole.USER, session);
+    tester.get(PermissionFacade.class).insertUserPermission(project.getId(), john.getId(), UserRole.USER, session);
+
+    session.commit();
+    session.clearCache();
+    tester.clearIndexes();
+
+    assertThat(index.getByKey(project.getKey())).isNull();
+    db.issueAuthorizationDao().synchronizeAfter(session, new Date(0));
+
+    IssueAuthorizationDoc issueAuthorizationDoc = index.getByKey(project.getKey());
+    assertThat(issueAuthorizationDoc).isNotNull();
+    assertThat(issueAuthorizationDoc.project()).isEqualTo("Sample");
+    assertThat(issueAuthorizationDoc.permission()).isEqualTo("user");
+    assertThat(issueAuthorizationDoc.groups()).containsExactly("devs");
+    assertThat(issueAuthorizationDoc.users()).containsExactly("john");
+
+    tester.clearIndexes();
+    tester.get(Platform.class).executeStartupTasks();
+    assertThat(index.getByKey(project.getKey())).isNotNull();
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/db/GroupDaoTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/db/GroupDaoTest.java
new file mode 100644 (file)
index 0000000..5a181da
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.user.db;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.core.persistence.AbstractDaoTestCase;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.user.GroupDto;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class GroupDaoTest extends AbstractDaoTestCase {
+
+  GroupDao dao;
+  DbSession session;
+  System2 system2;
+
+  @Before
+  public void setUp() {
+    this.session = getMyBatis().openSession(false);
+    this.system2 = mock(System2.class);
+    this.dao = new GroupDao(system2);
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    session.close();
+  }
+
+  @Test
+  public void select_by_key() {
+    setupData("select_by_key");
+
+    GroupDto group = dao.getByKey(session, "sonar-users");
+    assertThat(group).isNotNull();
+    assertThat(group.getId()).isEqualTo(1L);
+    assertThat(group.getName()).isEqualTo("sonar-users");
+    assertThat(group.getDescription()).isEqualTo("Sonar Users");
+    assertThat(group.getCreatedAt()).isEqualTo(DateUtils.parseDate("2014-09-07"));
+    assertThat(group.getUpdatedAt()).isEqualTo(DateUtils.parseDate("2014-09-08"));
+  }
+
+  @Test
+  public void insert() throws Exception {
+    when(system2.now()).thenReturn(DateUtils.parseDate("2014-09-08").getTime());
+
+    setupData("empty");
+
+    GroupDto dto = new GroupDto()
+      .setId(1L)
+      .setName("sonar-users")
+      .setDescription("Sonar Users");
+
+    dao.insert(session, dto);
+    session.commit();
+
+    checkTables("insert", "groups");
+  }
+}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/db/GroupDaoTest/empty.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/db/GroupDaoTest/empty.xml
new file mode 100644 (file)
index 0000000..a1c54e4
--- /dev/null
@@ -0,0 +1,4 @@
+<dataset>
+
+
+</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/db/GroupDaoTest/insert-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/db/GroupDaoTest/insert-result.xml
new file mode 100644 (file)
index 0000000..ebbd59a
--- /dev/null
@@ -0,0 +1,5 @@
+<dataset>
+
+  <groups id="1" name="sonar-users" description="Sonar Users" created_at="2014-09-08" updated_at="2014-09-08"/>
+
+</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/db/GroupDaoTest/select_by_key.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/db/GroupDaoTest/select_by_key.xml
new file mode 100644 (file)
index 0000000..e3ec011
--- /dev/null
@@ -0,0 +1,5 @@
+<dataset>
+
+  <groups id="1" name="sonar-users" description="Sonar Users" created_at="2014-09-07" updated_at="2014-09-08"/>
+
+</dataset>
index 58d40c935026afb2fcb71f11ffed128db92390a6..fb13bb56181282948ea1953a972a6e1e30f98a43 100644 (file)
@@ -167,7 +167,7 @@ public class MyBatis implements BatchComponent, ServerComponent {
       IssueMapper.class, IssueAuthorizationMapper.class, IssueStatsMapper.class, IssueChangeMapper.class, IssueFilterMapper.class, IssueFilterFavouriteMapper.class,
       LoadedTemplateMapper.class, MeasureFilterMapper.class, Migration44Mapper.class, PermissionTemplateMapper.class, PropertiesMapper.class, PurgeMapper.class,
       ResourceKeyUpdaterMapper.class, ResourceIndexerMapper.class, ResourceSnapshotMapper.class, RoleMapper.class, RuleMapper.class,
-      SchemaMigrationMapper.class, SemaphoreMapper.class, UserMapper.class, WidgetMapper.class, WidgetPropertyMapper.class,
+      SchemaMigrationMapper.class, SemaphoreMapper.class, UserMapper.class, GroupMapper.class, WidgetMapper.class, WidgetPropertyMapper.class,
       org.sonar.api.database.model.MeasureMapper.class, SnapshotDataMapper.class, SnapshotSourceMapper.class, ActionPlanMapper.class, ActionPlanStatsMapper.class,
       NotificationQueueMapper.class, CharacteristicMapper.class,
       GroupMembershipMapper.class, QualityProfileMapper.class, ActiveRuleMapper.class,
index f3afa89a920bdc0c05ea5bd982ed5d36db6d8050..b70d2526785c656aecde7fd4f75b99a24cb37b93 100644 (file)
  */
 package org.sonar.core.user;
 
+import org.sonar.core.persistence.Dto;
+
+import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
-import java.util.Date;
 
-/**
- * @since 3.2
- */
-public class GroupDto {
+public class GroupDto extends Dto<String> {
+
   private Long id;
   private String name;
   private String description;
-  private Date createdAt;
-  private Date updatedAt;
 
   public Long getId() {
     return id;
@@ -50,6 +48,7 @@ public class GroupDto {
     return this;
   }
 
+  @CheckForNull
   public String getDescription() {
     return description;
   }
@@ -59,21 +58,9 @@ public class GroupDto {
     return this;
   }
 
-  public Date getCreatedAt() {
-    return createdAt;
-  }
-
-  public GroupDto setCreatedAt(Date createdAt) {
-    this.createdAt = createdAt;
-    return this;
-  }
-
-  public Date getUpdatedAt() {
-    return updatedAt;
+  @Override
+  public String getKey() {
+    return name;
   }
 
-  public GroupDto setUpdatedAt(Date updatedAt) {
-    this.updatedAt = updatedAt;
-    return this;
-  }
 }
diff --git a/sonar-core/src/main/java/org/sonar/core/user/GroupMapper.java b/sonar-core/src/main/java/org/sonar/core/user/GroupMapper.java
new file mode 100644 (file)
index 0000000..0edd9c3
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.core.user;
+
+import javax.annotation.CheckForNull;
+
+public interface GroupMapper {
+
+  @CheckForNull
+  GroupDto selectByKey(String name);
+
+  void insert(GroupDto groupDto);
+
+}
index d4cf8ba718fa57f94537580592d871681c0c9554..eb451cf51ae75eb1d4f03cc8dab81e45810a8572 100644 (file)
@@ -6,10 +6,10 @@
 
   <select id="selectAfterDate" parameterType="map" resultType="map">
     SELECT
-      project_authorization.project as project,
-      project_authorization.login as user,
-      project_authorization.permission_group as permission_group,
-      project_authorization.permission_role as permission_role
+      project_authorization.project as "project",
+      project_authorization.login as "user",
+      project_authorization.permission_group as "permission_group",
+      project_authorization.permission_role as "permission_role"
     FROM (
       -- users
       SELECT
diff --git a/sonar-core/src/main/resources/org/sonar/core/user/GroupMapper.xml b/sonar-core/src/main/resources/org/sonar/core/user/GroupMapper.xml
new file mode 100644 (file)
index 0000000..4dd187b
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.core.user.GroupMapper">
+
+  <sql id="groupColumns">
+    g.id as id,
+    g.name as name,
+    g.description as description,
+    g.created_at as "createdAt",
+    g.updated_at as "updatedAt"
+  </sql>
+
+  <select id="selectByKey" parameterType="string" resultType="Group">
+    SELECT <include refid="groupColumns"/>
+    FROM groups g WHERE g.name=#{id}
+  </select>
+
+  <insert id="insert" parameterType="Group" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+    INSERT INTO groups (name, description, created_at, updated_at)
+    VALUES (#{name}, #{description}, #{createdAt}, #{updatedAt})
+  </insert>
+
+</mapper>