http://www.gaia.best by team seed π±
λ°νμμκ³Ό ν μ€νΈμμμ νμΈν΄λ³΄μΈμ
GAIAλ κΈ°μ‘΄μ Project Management Systemλ€μ μ΄λ €μ΄ μ¬μ©λ²κ³Ό λμ μ§μ
μ₯λ²½μ ν΄κ²°νκΈ° μν΄ κΈ°νλμμ΅λλ€.
μλμ λͺ¨λλ€μ ν΅ν΄ νλ‘μ νΈ κ΄λ¦¬μ κ°λ°μκ°μ νμ
μ λμ΅λλ€.
- μ΄μ νΈλνΉ ( Milestone, Issue )
- νλ‘μ νΈ μΌμ κ΄λ¦¬ ( Calendar, Gantt )
- μΉΈλ° λ³΄λ
- μν€
- λ΄μ€
- μΈμ€ν΄νΈ λ©μ μ
- νλ‘μ νΈ ν΅κ³
- ν΅ν© κ²μ
π Technology Stack
π©π©π¦π¦ Team members
π Award
λλμΈμ¬κ°λ°μ ν΄λΉ κΈ°μ μ΅μ°μ νλ‘μ νΈλ‘ μ μ λμμ΅λλ€.ππ
Gaia μκ°
1.Gaia
- Single Page Application
- URL Structure
- Elastic Search
2.Main
- index
- login
- admin
3.User
- overview
- notification
- alarm
- profile
- setting
- log
4.Chatting
- chat
5.Project
- code
- keyboard shortcut
- search
- multi languages
- milestone
- issue
- gantt
- calendar
- kanban
- news
- wiki
- analytics
- setting - member
- setting - management
1.Gaia
1) Single Page Application
gaiaλ μ±κΈ νμ΄μ§ μ΄ν리μΌμ΄μ μ λλ€.
νμν κ°μ’ ν¨μλ€μ λͺ¨λν μν€κ³ κΈ°μ΄λΆν° νλμ© μ€κ³νλ€λ³΄λ μ½μ§ μμ κ³Όμ μ΄μμ§λ§ ν΄λΌ μ μμμ΅λλ€. gaiaμμμ λͺ¨λ μμ²μ λΉλκΈ°λ‘ μ²λ¦¬λ©λλ€.
// λ€λ‘κ°κΈ° μ΄λ²€νΈ binding νκΈ°
$(window).bind("popstate", function(event) {
var data = event.originalEvent.state;
if(data){ // μ΄μ νμ΄μ§ λ°μ΄ν°κ° μμΌλ©΄ ajaxλ‘ λ€μ μμ²ν΄ νλ©΄ λ λλ§.
if(data.startsWith('member-')){
memberMovePage(data.substring('member-'.length));
}else{
movePage(data);
}
}else{ // νμ€ν 리μ μ λ³΄κ° μμκ²½μ° λ©μΈνλ©΄μΌλ‘ μ΄λμν€κΈ°.
var url = getContextPath();
$(location).attr('href',url);
}
})
// λ€λ‘κ°κΈ° μν©μ μ μΈνκ³ λ pushStateλ₯Ό ν΅ν΄ λ°μ΄ν°λ₯Ό μμμΌν©λλ€.
const movePageHistory = function(pageParam){
var url = getContextPath()
+'/'+manager_id+'/'+project_title
+ (pageParam!='code' ? '/'+pageParam : '');
history.pushState(pageParam, null, url);
movePage(pageParam);
}
pushStateλ₯Ό ν΅ν΄ λ°μ΄ν°λ₯Ό μκ³ popStateλ₯Ό binding ν΄μ λ€λ‘κ°κΈ°μ μμΌλ‘ κ°κΈ° μν©μ ν΄κ²°νμ΅λλ€.
2) URL Structure
λν pathvariableμ μ κ·Ήμ μΌλ‘ νμ©ν΄ URL κ·Έ μμ²΄κ° navigation μν μ λ체ν μ μλλ‘ κ΅¬ννμ΅λλ€.
μλ₯Ό λ€μ΄ μμ urlμΈ kkobuk/ddit302/issue/9 μ κ²½μ°μλ kkobukμ΄ μμ±ν ddit302 λΌλ νλ‘μ νΈμ 9λ²μ§Έ μ΄μλ₯Ό λ» ν©λλ€.
@Controller
@RequestMapping("{manager_id:^(?:(?!admin$|view$|restapi$).)*$}/{project_title:^(?:(?!overview$|help$|chat$|setting$).)*$}")
public class ProjectUrlMapper {
@Inject
private ProjectService service;
@Inject
private ProjectDao dao;
@Inject
private WebApplicationContext container;
private ServletContext application;
@PostConstruct
public void init() {
application = container.getServletContext();
}
private static final Logger logger = LoggerFactory.getLogger(ProjectUrlMapper.class);
@GetMapping({"","{pageParam}", "{pageParam}/{paramNo}"})
public String projectMenuOverview(
@PathVariable String manager_id
,@PathVariable String project_title
,@PathVariable Optional<String> pageParam
,@PathVariable Optional<String> paramNo
,Authentication authentication
,HttpSession session
,Model model
,HttpServletResponse resp
) {
// μ μμ€μΈ νλ‘μ νΈμ λν μ²λ¦¬λ₯Ό λ¨Όμ νλ€.
loadProjectProcessor(manager_id, project_title, authentication, session, resp);
// paramNo κ° μ‘΄μ¬ν λλ pageParamμ λΆμ¬μ€λ€.
if(paramNo.isPresent()) {
pageParam = Optional.of(String.format("%s/%s", pageParam.get(),paramNo.get()));
}
model.addAttribute("pageParam", pageParam.isPresent() ? pageParam.get() : "code");
model.addAttribute("manager_id", manager_id);
model.addAttribute("project_title", project_title);
return "view/template/project";
}
}
PathVariableκ³Ό μ κ·μμ νμ©ν΄ ꡬν νμ΅λλ€.
3) Elastic Search
@Component
public class ElasticUtil {
private String hostname;
private int port;
public String getHostname() {
return hostname;
}
public int getPort() {
return port;
}
private RestClientBuilder restClientBuilder;
private ElasticUtil() {
Properties properties = new Properties();
try { // dbinfo.propertiesμμ μ μ μ 보 λ°μμ΅λλ€.
properties.load(Resources.getResourceAsReader("best/gaia/db/dbinfo.properties"));
} catch (IOException e) {}
hostname = properties.getProperty("el.url");
port = Integer.parseInt(properties.getProperty("el.port"));
HttpHost host = new HttpHost(hostname, port);
restClientBuilder = RestClient.builder(host);
};
/**
* @param index
* @param query Map<String, Object> keyλ νλ‘νΌν°λͺ
, objectλ value 쑰건
* @param sort Map<String, SortOrder>
* @param size (null λ£μ μ μμ΅λλ€. size nullμΌ κ²½μ° λͺ¨λ λ°μμ΄)
* @return
*/
public List<Map<String,Object>> simpleSearch(
String index
, Map<String,Object> query
, Map<String,SortOrder> sort
, Integer size
){
// searchμ index 쑰건 κ±ΈκΈ°
SearchRequest searchRequest = new SearchRequest(index);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// queryμ μλ μ
쿼리 쑰건μΌλ‘ κ±ΈκΈ°
for(String key : query.keySet()) {
searchSourceBuilder.query(QueryBuilders.matchQuery(key, query.get(key)));
}
// sort μ μλ μ
μ μ λ ¬ 쑰건μΌλ‘ κ±ΈκΈ°
for(String key : sort.keySet()) {
searchSourceBuilder.sort(new FieldSortBuilder(key).order(sort.get(key)));
}
if(size != null) {
searchSourceBuilder.size(size);
}else {
searchSourceBuilder.size(200);
}
searchRequest.source(searchSourceBuilder);
List<Map<String,Object>> list = new ArrayList<>();
try(RestHighLevelClient client = new RestHighLevelClient(restClientBuilder)) {
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits searchHits = response.getHits();
for(SearchHit hit : searchHits) {
Map<String, Object> sourceMap = hit.getSourceAsMap();
list.add(sourceMap);
}
} catch (IOException e) {}
return list;
}
public int insert(String index, Map<String, Object> data ){
IndexResponse response = null;
try(RestHighLevelClient client = new RestHighLevelClient(restClientBuilder)) {
data.put("date", LocalDateTime.now());
XContentBuilder xContent = XContentFactory.jsonBuilder().map(data);
String jsonBody = Strings.toString(xContent);
// id μμ΄ μ½μ
μ μλ UIDκ° μμ±λ©λλ€.
String id = null;
IndexRequest indexRequest = new IndexRequest(index).id(id).source(jsonBody, XContentType.JSON);
response = client.index(indexRequest, RequestOptions.DEFAULT);
} catch (IOException e) {}
return response.getShardInfo().getSuccessful();
}
}
Elastic Searchμ highlevel java client APIλ₯Ό νμ©ν λͺ¨λμ λ§λ€μ΄μ μ½κ² μ¬μ©νμ΅λλ€.
2.Main
GAIAμ λ©μΈ νλ©΄ μ λλ€.
μ°μΈ‘ μλ¨ λ‘κ·ΈμΈ λ²νΌμ λλ¬ λ‘κ·ΈμΈ ν μ μμ΅λλ€.
νμκ°μ μμ μ°ν λ²νΌμ ν΄λ¦ νλ©΄ μ¨κ²¨μ§ μλ² κ΄λ¦¬μ λͺ¨λλ‘ μ§μ ν μ μμ΅λλ€.
ν΄λΉ νμ΄μ§μμλ μ΄μμ€μΈ μλ²μ λ€μν μ 보λ₯Ό μ€μκ°μΌλ‘ νμΈ ν μ μμ΅λλ€. μ§κΈμ Oracle cloudμ Ubuntu instanceμμ μλ²λ₯Ό ꡬλμ€μ΄λΌμ OS informationμ LINUXλ‘ λμ€λ κ²μ΄ νμΈλ©λλ€.
3.User
μ²μ λ‘κ·ΈμΈ νμλ νμ΄μ§ μ λλ€. μ’μΈ‘μλ λ΄κ° μν΄μλ νλ‘μ νΈλ€μ΄, μ€μλΆμλ λμκ² ν λΉλ μ΄μλ€μ΄ 보μ¬μ§λλ€.
λκ΅°κ°κ° μ μμ νκ±°λ λ΄κ° μ΄ μ΄μμ λκΈμ λ¬λ©΄ push μλμ λ°μ΅λλ€. μ°μΈ‘ μλ¨μ νμλ©λλ€.
μ’ λͺ¨μ μμ΄μ½μ ν΄λ¦ν΄μ μλλ€μ νμΈ ν μ μμ΅λλ€.
νλ‘ν μ 보λ₯Ό μμ ν μ μμΌλ©°
μ΄λ¦κ³Ό λΉλ°λ²νΈλ₯Ό λ³κ²½ ν μ μμ΅λλ€.
μ μ μ΄λ ₯μ elastic searchμ λΉκ΄κ³ν λ°μ΄ν°λ² μ΄μ€λ‘ κ΄λ¦¬λ©λλ€.
4.Chatting
κ°λ¨ν μ±ν κΈ°λ₯λ μ€λΉλμ΄ μμ΅λλ€.
5.Project
1) Code
νλ‘μ νΈμ λ€μ΄κ°λ©΄ 첫 νμ΄μ§μΈ Code νμ΄μ§ μ λλ€. Githubμ μλ repositoryμ μ°λν΄μ Codeμ readme νμΌμ κ°μ Έμ΅λλ€. μ°μΈ‘μλ νλ‘μ νΈμ λν μ€λͺ κ³Ό λ©€λ²λ€ λͺ©λ‘μ΄ λμ΅λλ€.
2) Keyboard Shortcuts
μμ°μ± ν₯μμ μν λ¨μΆν€ κΈ°λ₯μ μ 곡ν©λλ€. Ctrl + '/' ν€λ‘ λ¨μΆν€ λͺ©λ‘μ νμΈ ν μ μμ΅λλ€.
3) Search
ν΅ν© κ²μ κΈ°λ₯μ μ 곡ν©λλ€. Keyλ₯Ό μ λ ₯ν λ λ§λ€ λ°λ‘λ°λ‘ κ²μ ν΄ μ€λλ€. Logstashλ‘ Oracle μλ²λ₯Ό Elastic Searchμ μΈλ±μ± ν΄μ ꡬν νμμ΅λλ€.
4) Multi languages
λ€κ΅μ΄ λ©λ΄λ₯Ό μ§μν©λλ€. λ©λ΄λ νλμ½λ© λμ΄ μμ§ μκ³ Database μμ λ°μμ€κΈ° λλ¬Έμ κ°λ¨νκ² λ©λ΄λ₯Ό μΆκ°νκ±°λ μΈμ΄λ₯Ό μΆκ°ν μ μμ΅λλ€.
5) Milestone
λ§μΌμ€ν€ λͺ©λ‘μ νμΈ ν μ μμ΅λλ€. κ°κ° λ§μΌμ€ν€μ μ§νλλ₯Ό νλμ νμΈ ν μ μμ΅λλ€.
κ°λ³ λ§μΌμ€ν€μ μ‘°ν νλ©΄ ν΄λΉ λ§μΌμ€ν€μ μν μ΄μλ€μ μ‘°ν ν μ μμ΅λλ€.
6) Issue
μ΄μ λͺ©λ‘ νμ΄μ§μμλ κ° νν°λ³λ‘ μ΄μλ€μ νν°λ§ ν΄μ μ‘°ν ν μ μμ΅λλ€.
λ΄λΉμ λͺ©λ‘μ λ§μ°μ€λ₯Ό μ¬λ¦¬λ©΄ νΌμ³μ 보μ¬μ€λλ€. cssλ‘ κ΅¬ν νμ΅λλ€.
μλ‘μ΄ μ΄μλ₯Ό μμ± ν μ μμ΅λλ€.
κ°λ¨νκ² μμ λ ν μ μκ³ λκΈλ μμ± ν©λλ€.
μν©μλ°λΌ λ‘/μΌλ‘ μ/λ₯Όκ³Ό κ°μ μ‘°μ¬λ₯Ό ꡬλΆν©λλ€. μλμ μ½λλ‘ κ΅¬ννμ΅λλ€.
// λ°μΉ¨μ΄ μλ λ¬ΈμμΈμ§ ν
μ€νΈ ν΄μ£Όλ ν¨μ μ
λλ€.
const isSingleCharacter = function(text) {
var strGa = 44032; // κ°
var strHih = 55203; // ν£
var lastStrCode = text.charCodeAt(text.length-1);
if(lastStrCode < strGa || lastStrCode > strHih) {
return false; //νκΈμ΄ μλ κ²½μ° false λ°ν
}
return (( lastStrCode - strGa ) % 28 == 0)
}
// 'λ‘' κ° λΆμ΄μΌ νλμ§ 'μΌλ‘'κ° λΆμ΄μΌ νλμ§ μ²΄ν¬ν΄μ£Όλ ν¨μ
const roChecker = function(text){
return text + (isSingleCharacter(text)? 'λ‘' : 'μΌλ‘');
}
// 'λ₯Ό' μ΄ λΆμ΄μΌ νλμ§ 'μ'μ΄ λΆμ΄μΌ νλμ§λ₯Ό 체ν¬ν΄μ£Όλ ν¨μ
const rulChecker = function(text){
return text + (isSingleCharacter(text)? 'λ₯Ό' : 'μ');
}
νκΈ μ’ μ±μ΄ μ΄ 28κ° μΈ κ²μ νμ©ν΄ μ½λλ₯Ό μμ± νμ΅λλ€.
7) Gantt
8) Calendar
9) Kanban
μΉΈλ° κΈ°λ₯λ ꡬννμ΅λλ€. κ°κ°μ μΉΈλ° Columnκ³Ό Cardλ€μ Singly linked listλ‘ μ°κ²° λμ΄ μμ΅λλ€. ν΄λΉ λΉμ¦λμ€ λ‘μ§μ μλμ κ°μ΅λλ€.
@Override
@Transactional
public ServiceResult moveCard(Integer droppedCardNo, Integer newColumnNo, Integer nextCardNo) {
// λλλ μΉ΄λ μ 보 λ°μμ€κΈ°
KanbanCardVO droppedCard = kanbanDao.selectCard(droppedCardNo);
KanbanCardVO previousNextCard = null;
KanbanCardVO currentNextCard = null;
// λλλ μΉ΄λμ previousNextCard μ 보
if (droppedCard.getKb_card_next_no() != null) {
previousNextCard = kanbanDao.selectCard(droppedCard.getKb_card_next_no());
}
// λλλ μΉ΄λμ νμ¬ λ€μ μΉ΄λ μ 보
if (nextCardNo != null) {
currentNextCard = kanbanDao.selectCard(nextCardNo);
}
if (previousNextCard != null) {
// previousNextCardκ° droppedCardμ priv_noλ₯Ό priv_no λ‘ κ°μ§λ€.
previousNextCard.setKb_card_priv_no(droppedCard.getKb_card_priv_no());
}
// droppedCardμ μ΄μ μΉ΄λ μ 보λ₯Ό νμ¬ λ€μ μΉ΄λμ μ΄μ μΉ΄λ λλ²μμ κ°μ Έμ μμ νλ€.
if (currentNextCard == null) {
// μλ‘μ΄ μ리μ λ€μ μΉ΄λκ° μλ€λ©΄, μ΄μ¬ μ¨ μ»¬λΌμ λ§μ§λ§ μΉ΄λλ₯Ό priv_no λ‘ κ°λλ€.
Integer lastCardNo = kanbanDao.getLastCardNo(newColumnNo);
droppedCard.setKb_card_priv_no(lastCardNo);
} else {
// μλ‘μ΄ μ리μ λ€μ μΉ΄λκ° μμΌλ©΄ ν΄λΉ μΉ΄λμ μ΄μ μΉ΄λ λ²νΈλ₯Ό λΊμ΄μ¨λ€.
droppedCard.setKb_card_priv_no(currentNextCard.getKb_card_priv_no());
// νμ¬ λ€μ μΉ΄λμ μ΄μ μΉ΄λ μ 보λ₯Ό dropped card no λ‘ μμ νλ€.
currentNextCard.setKb_card_priv_no(droppedCardNo);
}
// droppedCardμ column κ°μ νμ¬ λ€μ μΉ΄λμ column κ°μΌλ‘ μμ νλ€.
droppedCard.setKb_col_no(newColumnNo);
// λ³κ²½μ΄ μμλ μΈ κ°μ μΉΈλ° μΉ΄λ μ 보λ₯Ό λͺ¨λ μ
λ°μ΄νΈ νλ€.
// 쿼리λ₯Ό μΈλ² μμ§λ§, νλμ νΈλμμ
μΌλ‘ κ΄λ¦¬
int validChecker = 1;
if (previousNextCard != null) {
validChecker *= kanbanDao.updateCard(previousNextCard);
}
validChecker *= kanbanDao.updateCard(droppedCard);
if (currentNextCard != null) {
validChecker *= kanbanDao.updateCard(currentNextCard);
}
if (validChecker == 1) {
return ServiceResult.OK;
} else {
return ServiceResult.FAIL;
}
}
μ κ° μλμ μΉΈλ°μ μ’μν΄μ νμμλ ν¬μ€νΈμμ νμ©ν μΉΈλ°μ μ κ·Ήμ μΌλ‘ νμ© νμ΅λλ€.
10)News
λ΄μ€ νμ΄μ§ μ λλ€. 무ν μ€ν¬λ‘€λ‘ νμ΄μ§ μ²λ¦¬ νμμ΅λλ€.
11)Wiki
μν€ νμ΄μ§μμλ κ° μν€λ³ μμ λ΄μλν μ‘°ν ν μ μμ΅λλ€.
12)Analytics
νλ‘μ νΈμ κ°μ’ ν΅κ³λ₯Ό νμΈ ν μ μλ νμ΄μ§ μ λλ€.
13)Member
λ©€λ² κ΄λ¦¬ νμ΄μ§μμλ μμλ λ©€λ²λ€μ μ‘°ννκ³ , λ©€λ²μ κΆνμ λΆμ¬ ν μ μμΌλ©° μ΄λ νΉμ νν΄λ₯Ό μν¬ μ μμ΅λλ€.
14)Management
νλ‘μ νΈλ₯Ό κ΄λ¦¬νλ νμ΄μ§ μ λλ€.
μλ‘μ΄ λΌλ²¨μ μμ±ν λλ 컀μ€ν°λ§μ΄μ§ ν μ μλλ‘ νμ΅λλ€.
μ¬μ©λͺ¨λ, μ΄μμ€μλ, κ·Έλ¦¬κ³ κΆνμ κ²½μ°μλ 컬λΌμ μ¬λ¬κ° λ§λ€ νμ μλλ‘ μ΄μ§μ ννλ‘ κ°μ μ μ₯νλλ‘ κ΅¬ννμ΅λλ€.
μ΄μμ λλ€. ν΄λΉ κΈμ readme νμΌ μμ±μ μν΄ markdown μΌλ‘ μμ± λμμ΅λλ€.
Github μμ readme νμΌμ κΎΈμ€ν μ λ‘λ ν μμ μ΄μ§λ§ ν°μ€ν 리μμμ markdown μ§μμ΄ λΉμ½ν΄μ ν΄λΉ ν¬μ€ν μ λμ΄μ μ λ°μ΄νΈ νμ§ μμ΅λλ€. μλ λ§ν¬μμ μ΅μ μ readme νμΌμ νμΈν μ μμ΅λλ€.
https://github.com/ddit301/gaia
ddit301/gaia
final project gaia. Contribute to ddit301/gaia development by creating an account on GitHub.
github.com