diff --git a/.erd b/.erd new file mode 100644 index 0000000..2d21af8 --- /dev/null +++ b/.erd @@ -0,0 +1,4097 @@ +{ + "$schema": "https://raw.githubusercontent.com/dineug/erd-editor/main/json-schema/schema.json", + "version": "3.0.0", + "settings": { + "width": 2800, + "height": 2800, + "scrollTop": -223, + "scrollLeft": -817.1811, + "zoomLevel": 0.82, + "show": 431, + "database": 16, + "databaseName": "", + "canvasType": "ERD", + "language": 1, + "tableNameCase": 4, + "columnNameCase": 2, + "bracketType": 1, + "relationshipDataTypeSync": true, + "relationshipOptimization": false, + "columnOrder": [ + 1, + 2, + 4, + 8, + 16, + 32, + 64 + ], + "maxWidthComment": -1, + "ignoreSaveSettings": 0 + }, + "doc": { + "tableIds": [ + "-5qu8nMzz6rFtw4LgAYlu", + "9U9lcHeB3nyao9FCFAdPC", + "ZECn5lpVsAoZptTLtCY3Q", + "cga_ec1K1CMdP-kmiMOaU", + "V2F5Frw7sCNzkLLdij-UM", + "J0PRQVyskI51G4qHRo5S0", + "d9O9qp4Bp-WjuzThHIuxE", + "DFKh-HfnYWxi11eZCqkgK", + "IkMA_k3YSf7oXRAgIqgWR", + "DbdrGyXt-E-JbuoSHJ2iE", + "himTd51e5v3kRUyGYe4NN", + "qqI7n1cZsxqVUkosICW5E", + "_6YaIHBhh5zFfzCmAXN92", + "Xq4nUH16urgOVDvzpQpiX", + "fDbodrcUhn-ojdGXpBqXU", + "dMgB23-XyfyBrQtln8T6A", + "Ii0qrARt-8gDVEMzVY7mM", + "trYMd6wdMNDvKmaoWNXIx", + "EQAGETeXaGT758Qm7scqi", + "cnbWJvE8hHTzCVJCwH6na", + "7_V9K4Pn6pwpgUiJ7FT-V", + "ZwKFlfp-W6ARlfved6xt2", + "yzPCT3ZlJYYiwy0SG_8oH", + "wARo-MUXMFDwfjCGKVOMl", + "-q6IWAb20IgbjrvUkuOFY", + "kbP9od-gNNwXSElu7qaNa", + "r5gRtDj458FLG5FFs-ny7", + "6IEGvqpssK0H42uHqPSNK", + "zP_GWMpvpT6wH7qn9vxdv", + "Yw3xkCepE7qeZkn3zo3TY", + "xGf94aIdnNFbMawDHNsnn", + "_IBhvMR9bIYR0UYXq65hA", + "kYx9YWvvOS89pMYnChmLJ", + "tcjXlQrjNpKf54GokA_l5", + "xoqfDhL3amNw6Xb-_L0lq", + "665LiQRcz_kr-JL4DTwwB", + "Bs8zWea7vJ_7NbzaxlAxU", + "BQS6494_Cs422SQSbfJ2n", + "fMdGmsjMSQ2cLtrNcE1gk", + "P4rpGmH6RmTcoRD_sWJW4", + "O6gR-_ZtKDlJpb0wAElPA", + "_EAn5ShGQYKY-ptV_vVFZ", + "jEo7qsqlX6uXALZ7P85iK" + ], + "relationshipIds": [ + "ZcMO2M-zi0H67MLvkQFeA", + "qA2OY8Vv0tB0-YE4YpkJC", + "9Hb7Dk-7X8s_d8BxRf8sy", + "lBM6uJcLX1w5yGduCTtBc", + "RK0Atmk9dGVKv06yDhVUH", + "IY-Yho5L0plKCcbyO8BwY", + "vLKcVXyuGv1IfCGVIOB8e", + "vdEfrV3BTtVAnn8Q73CDu", + "UhArtDWdx6IjXzb8LLO4G", + "C-o6_l-NoeEaj7C9H8Mp6", + "sJsxGEyjjZY4WmbJW9z6x", + "nNXynoaZirFpqYagY9Pp1", + "QJtEubwRLcmguXwd7VD6y", + "sJyqru4WOO84I83NayBmj", + "HBFcb8g6icKs1QYEYfKGu", + "tmg5QWYb-Lx9oVhjFj8v9", + "g48QHmvkdOuciC6Bk_q0o", + "9QMmEY_TEQZNT5sDmA6Yz", + "uZYdKkxyiZvHumfOOANY1", + "gaL7EcdRZlWLEmqGGQXM2", + "B1WkiiWsU0UzYoNPS3q8Z", + "co1cKJ8nBRLt8YAfd1tdb", + "MvtwedswfxkFsNDZKsJ6b" + ], + "indexIds": [], + "memoIds": [ + "kCNm2V-KbwITsahSuWOCX", + "tqgaaX4eEzP1nibRXVfGg", + "pF_-UJJxAyfhWI3x2tlPZ", + "rKhGovSsu7i1je5nndRU9" + ] + }, + "collections": { + "tableEntities": { + "GdxhuSk4fivseld2LfpQ_": { + "id": "GdxhuSk4fivseld2LfpQ_", + "name": "test", + "comment": "테스트", + "columnIds": [ + "W4DdKpCeycWvaeeNXYOFt", + "gOEACnjl8oIYmTLgFTsbh" + ], + "seqColumnIds": [ + "W4DdKpCeycWvaeeNXYOFt", + "gOEACnjl8oIYmTLgFTsbh" + ], + "ui": { + "x": 114, + "y": 174, + "zIndex": 2, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1754382276515, + "createAt": 1754381156557 + } + }, + "qhOl9wIjJqrVXh-bSv7ke": { + "id": "qhOl9wIjJqrVXh-bSv7ke", + "name": "test2", + "comment": "", + "columnIds": [ + "Fl3HXhOKPtdW69eynS0yd", + "0lyscs5BYOyHDM_xXE4At" + ], + "seqColumnIds": [ + "Fl3HXhOKPtdW69eynS0yd", + "0lyscs5BYOyHDM_xXE4At" + ], + "ui": { + "x": 721, + "y": 260, + "zIndex": 14, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1754381199114, + "createAt": 1754381170164 + } + }, + "-5qu8nMzz6rFtw4LgAYlu": { + "id": "-5qu8nMzz6rFtw4LgAYlu", + "name": "", + "comment": "사용자", + "columnIds": [ + "qhvoE6d1E7XPv_qZioXn9", + "Hp-tw27qQZE9E-_qygiiX" + ], + "seqColumnIds": [ + "qhvoE6d1E7XPv_qZioXn9", + "Hp-tw27qQZE9E-_qygiiX" + ], + "ui": { + "x": 647, + "y": 44, + "zIndex": 2, + "widthName": 60, + "widthComment": 60, + "color": "#00BCD4" + }, + "meta": { + "updateAt": 1754441134504, + "createAt": 1754382285974 + } + }, + "9U9lcHeB3nyao9FCFAdPC": { + "id": "9U9lcHeB3nyao9FCFAdPC", + "name": "", + "comment": "조직도(부서)", + "columnIds": [ + "_yLtTO_rw7k6cJYjfgK8m" + ], + "seqColumnIds": [ + "mMcdNY5ZMyG7RWYIWrfCP", + "_yLtTO_rw7k6cJYjfgK8m" + ], + "ui": { + "x": 1269.8658, + "y": 54.433, + "zIndex": 6, + "widthName": 60, + "widthComment": 69, + "color": "#00BCD4" + }, + "meta": { + "updateAt": 1754442341677, + "createAt": 1754382295651 + } + }, + "ZECn5lpVsAoZptTLtCY3Q": { + "id": "ZECn5lpVsAoZptTLtCY3Q", + "name": "", + "comment": "아이템", + "columnIds": [ + "VtPDZvC98RevCb-BNU1I2" + ], + "seqColumnIds": [ + "VtPDZvC98RevCb-BNU1I2" + ], + "ui": { + "x": 1442, + "y": 235.9691, + "zIndex": 26, + "widthName": 60, + "widthComment": 60, + "color": "#00BCD4" + }, + "meta": { + "updateAt": 1754441144866, + "createAt": 1754382380662 + } + }, + "cga_ec1K1CMdP-kmiMOaU": { + "id": "cga_ec1K1CMdP-kmiMOaU", + "name": "", + "comment": "과제", + "columnIds": [ + "Wl2wIBv5ZUdsDNDyqqms0", + "-jlLf-KlnCypV1RSu3VL4" + ], + "seqColumnIds": [ + "Wl2wIBv5ZUdsDNDyqqms0", + "-jlLf-KlnCypV1RSu3VL4" + ], + "ui": { + "x": 1441, + "y": 536, + "zIndex": 31, + "widthName": 60, + "widthComment": 60, + "color": "#00BCD4" + }, + "meta": { + "updateAt": 1754441147845, + "createAt": 1754382390306 + } + }, + "V2F5Frw7sCNzkLLdij-UM": { + "id": "V2F5Frw7sCNzkLLdij-UM", + "name": "", + "comment": "과제 권한 정보", + "columnIds": [ + "VlovfgG2esD00MI11Q6tK" + ], + "seqColumnIds": [ + "VlovfgG2esD00MI11Q6tK" + ], + "ui": { + "x": 980.1322, + "y": 521.902, + "zIndex": 35, + "widthName": 60, + "widthComment": 81, + "color": "#00BCD4" + }, + "meta": { + "updateAt": 1754465520446, + "createAt": 1754382403536 + } + }, + "J0PRQVyskI51G4qHRo5S0": { + "id": "J0PRQVyskI51G4qHRo5S0", + "name": "", + "comment": "과제 열람 권한", + "columnIds": [ + "AqyFYfhQmIEV8wjjvCze_", + "MsFBx7x5Ad7B5Ey3Hm8bz" + ], + "seqColumnIds": [ + "4hqwsbU_xyBkx2utb4t3j", + "AqyFYfhQmIEV8wjjvCze_", + "tqlJcNxMkT7ecK169-PIb", + "d7sqF3TYGv3AW9uoqpr67", + "MsFBx7x5Ad7B5Ey3Hm8bz", + "v62zqHVW5hq6fn1wU83kl" + ], + "ui": { + "x": 983.2577, + "y": 668.1756, + "zIndex": 54, + "widthName": 60, + "widthComment": 81, + "color": "" + }, + "meta": { + "updateAt": 1754461881896, + "createAt": 1754382496947 + } + }, + "d9O9qp4Bp-WjuzThHIuxE": { + "id": "d9O9qp4Bp-WjuzThHIuxE", + "name": "", + "comment": "열람 요청 및 승인 이력", + "columnIds": [ + "3E8-LrDjIaiAqWqeEvZ2C" + ], + "seqColumnIds": [ + "jUn43teJpwa9tPgNJtzNw", + "3E8-LrDjIaiAqWqeEvZ2C" + ], + "ui": { + "x": 982.8235, + "y": 872.7044, + "zIndex": 73, + "widthName": 60, + "widthComment": 123, + "color": "" + }, + "meta": { + "updateAt": 1754464814333, + "createAt": 1754382591918 + } + }, + "DFKh-HfnYWxi11eZCqkgK": { + "id": "DFKh-HfnYWxi11eZCqkgK", + "name": "", + "comment": "NGS 작업 스케줄", + "columnIds": [ + "7KudidjQAZzTzaUZVF6iZ", + "qQgNRyMv4DeRjGHkWsbKQ", + "Qni8gmYzxmgRQpm92u3jV", + "Xqf75OtW1XCwmT8ZaOulD" + ], + "seqColumnIds": [ + "Sp78cophNBj4x_2PStbBx", + "7KudidjQAZzTzaUZVF6iZ", + "9SPqiG6vkNx8IX8s95-Sa", + "998hQJ57qiQvk3rpO5soW", + "qQgNRyMv4DeRjGHkWsbKQ", + "Qni8gmYzxmgRQpm92u3jV", + "Xqf75OtW1XCwmT8ZaOulD" + ], + "ui": { + "x": 1221.8926, + "y": 1240.192, + "zIndex": 94, + "widthName": 60, + "widthComment": 92, + "color": "" + }, + "meta": { + "updateAt": 1754460637697, + "createAt": 1754437948039 + } + }, + "IkMA_k3YSf7oXRAgIqgWR": { + "id": "IkMA_k3YSf7oXRAgIqgWR", + "name": "", + "comment": "사용자 마이폴더 항목", + "columnIds": [ + "8LERJxERBdC27ggCEei2r" + ], + "seqColumnIds": [ + "Y5cGoKRT8kCo4xAECN3M8", + "8LERJxERBdC27ggCEei2r", + "568TVy-QhY4hkD5t6Blcn" + ], + "ui": { + "x": 575.407, + "y": 250.0684, + "zIndex": 99, + "widthName": 60, + "widthComment": 117, + "color": "" + }, + "meta": { + "updateAt": 1754460134995, + "createAt": 1754437970916 + } + }, + "DbdrGyXt-E-JbuoSHJ2iE": { + "id": "DbdrGyXt-E-JbuoSHJ2iE", + "name": "", + "comment": "권한 그룹", + "columnIds": [ + "cn39aiZvY7EKbXdDNv8UJ", + "8HtDMkqi7YdcommwW4p8J" + ], + "seqColumnIds": [ + "cn39aiZvY7EKbXdDNv8UJ", + "1bcrpFyPZVpvYGRKXZCc9", + "8HtDMkqi7YdcommwW4p8J" + ], + "ui": { + "x": 106.8455, + "y": 56.3709, + "zIndex": 215, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1754440685037, + "createAt": 1754439964334 + } + }, + "himTd51e5v3kRUyGYe4NN": { + "id": "himTd51e5v3kRUyGYe4NN", + "name": "", + "comment": "리소스(URI)", + "columnIds": [ + "8j7EyYMI81x9vyXb19mYx" + ], + "seqColumnIds": [ + "Zvo2LUN2HExSyxH4vLXUc", + "8j7EyYMI81x9vyXb19mYx" + ], + "ui": { + "x": 99.4433, + "y": 472.9795, + "zIndex": 239, + "widthName": 60, + "widthComment": 64, + "color": "" + }, + "meta": { + "updateAt": 1754460165674, + "createAt": 1754440145075 + } + }, + "qqI7n1cZsxqVUkosICW5E": { + "id": "qqI7n1cZsxqVUkosICW5E", + "name": "", + "comment": "권한 그룹 - 리소스", + "columnIds": [ + "44a4KJr3AYVwaAHlgvTsQ", + "UUt2XNO5QUbcMpXrVoa5L" + ], + "seqColumnIds": [ + "44a4KJr3AYVwaAHlgvTsQ", + "UUt2XNO5QUbcMpXrVoa5L" + ], + "ui": { + "x": 107.2159, + "y": 259.7933, + "zIndex": 271, + "widthName": 60, + "widthComment": 101, + "color": "" + }, + "meta": { + "updateAt": 1754460163137, + "createAt": 1754440643293 + } + }, + "_6YaIHBhh5zFfzCmAXN92": { + "id": "_6YaIHBhh5zFfzCmAXN92", + "name": "", + "comment": "베크만 장비 연동 테이블", + "columnIds": [ + "Zod_8Knm5VO7zg5tahBP4" + ], + "seqColumnIds": [ + "Zod_8Knm5VO7zg5tahBP4" + ], + "ui": { + "x": 1217.9279, + "y": 2170.191, + "zIndex": 287, + "widthName": 60, + "widthComment": 132, + "color": "#00BCD4" + }, + "meta": { + "updateAt": 1754527993076, + "createAt": 1754440889672 + } + }, + "Xq4nUH16urgOVDvzpQpiX": { + "id": "Xq4nUH16urgOVDvzpQpiX", + "name": "", + "comment": "SCADA 연동 테이블", + "columnIds": [ + "sqasH1nnOalUWKNdS4p7X" + ], + "seqColumnIds": [ + "sqasH1nnOalUWKNdS4p7X" + ], + "ui": { + "x": 1216.0927, + "y": 1525.9486, + "zIndex": 306, + "widthName": 60, + "widthComment": 106, + "color": "#00BCD4" + }, + "meta": { + "updateAt": 1754527826724, + "createAt": 1754441107636 + } + }, + "fDbodrcUhn-ojdGXpBqXU": { + "id": "fDbodrcUhn-ojdGXpBqXU", + "name": "", + "comment": "업로드 파일", + "columnIds": [], + "seqColumnIds": [], + "ui": { + "x": 123.5699, + "y": 1015.4639, + "zIndex": 324, + "widthName": 60, + "widthComment": 65, + "color": "" + }, + "meta": { + "updateAt": 1754459568670, + "createAt": 1754442607468 + } + }, + "dMgB23-XyfyBrQtln8T6A": { + "id": "dMgB23-XyfyBrQtln8T6A", + "name": "", + "comment": "유전체", + "columnIds": [ + "4QYIJ98XjTW50SND0UHEI", + "tUSO7tRNXuublZhwP2cBW" + ], + "seqColumnIds": [ + "A_WYW7xfYR-PnMjxumPc_", + "4QYIJ98XjTW50SND0UHEI", + "CSEVGwHrf8BH60Mzb3xYT", + "tUSO7tRNXuublZhwP2cBW", + "YkQGVKARv46Tmp7L2zRCP", + "aCkS4Nc3SibCy7Y-AdeVg" + ], + "ui": { + "x": 2400.8898, + "y": 43.2988, + "zIndex": 331, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1754462290977, + "createAt": 1754443520684 + } + }, + "Ii0qrARt-8gDVEMzVY7mM": { + "id": "Ii0qrARt-8gDVEMzVY7mM", + "name": "", + "comment": "서열 분석 결과", + "columnIds": [ + "o-AQaBMbBs0mLny2XcJ5p", + "WTuECNDiy8M-4TbJkAMZr" + ], + "seqColumnIds": [ + "gVGl0fqaEpNpPcZIv5DFK", + "o-AQaBMbBs0mLny2XcJ5p", + "WTuECNDiy8M-4TbJkAMZr" + ], + "ui": { + "x": 117.3848, + "y": 1334.0203, + "zIndex": 342, + "widthName": 60, + "widthComment": 81, + "color": "" + }, + "meta": { + "updateAt": 1754459563276, + "createAt": 1754443583844 + } + }, + "trYMd6wdMNDvKmaoWNXIx": { + "id": "trYMd6wdMNDvKmaoWNXIx", + "name": "", + "comment": "유전체 열람 권한", + "columnIds": [ + "WQM5_fqs1JPU5tMld5lkV" + ], + "seqColumnIds": [ + "WQM5_fqs1JPU5tMld5lkV" + ], + "ui": { + "x": 1902.0617, + "y": 54.6389, + "zIndex": 420, + "widthName": 60, + "widthComment": 93, + "color": "" + }, + "meta": { + "updateAt": 1754461900277, + "createAt": 1754445545793 + } + }, + "EQAGETeXaGT758Qm7scqi": { + "id": "EQAGETeXaGT758Qm7scqi", + "name": "", + "comment": "서열 파일(ab1, gbk, assembly)", + "columnIds": [ + "6wZVzAGVd1XHSDSy4C9j5" + ], + "seqColumnIds": [ + "AJ4VUft8Sd_MI-ze0I8O0", + "6wZVzAGVd1XHSDSy4C9j5" + ], + "ui": { + "x": 114.6548, + "y": 1137.1129, + "zIndex": 490, + "widthName": 60, + "widthComment": 161, + "color": "" + }, + "meta": { + "updateAt": 1754459565521, + "createAt": 1754453243813 + } + }, + "cnbWJvE8hHTzCVJCwH6na": { + "id": "cnbWJvE8hHTzCVJCwH6na", + "name": "", + "comment": "NGS 장비 메타 정보", + "columnIds": [ + "DVHJQN88K3BepltoeGPDP" + ], + "seqColumnIds": [ + "DVHJQN88K3BepltoeGPDP" + ], + "ui": { + "x": 1236.5185, + "y": 982.9305, + "zIndex": 538, + "widthName": 60, + "widthComment": 107, + "color": "" + }, + "meta": { + "updateAt": 1754465783826, + "createAt": 1754453726641 + } + }, + "7_V9K4Pn6pwpgUiJ7FT-V": { + "id": "7_V9K4Pn6pwpgUiJ7FT-V", + "name": "", + "comment": "NGS 작업 이력", + "columnIds": [ + "vRXMqti-9v-ydmvKX8Ht7", + "er5BYXkeByO3tF7TaelR-", + "MYB8w1R7Zwiykn_daUJka" + ], + "seqColumnIds": [ + "vRXMqti-9v-ydmvKX8Ht7", + "er5BYXkeByO3tF7TaelR-", + "MYB8w1R7Zwiykn_daUJka" + ], + "ui": { + "x": 1688.8813, + "y": 1375.6009, + "zIndex": 584, + "widthName": 60, + "widthComment": 80, + "color": "" + }, + "meta": { + "updateAt": 1754460713991, + "createAt": 1754454042712 + } + }, + "ZwKFlfp-W6ARlfved6xt2": { + "id": "ZwKFlfp-W6ARlfved6xt2", + "name": "", + "comment": "의뢰(DLIMS)", + "columnIds": [ + "urO6c-0U2Cf3ELSLcqydc" + ], + "seqColumnIds": [ + "urO6c-0U2Cf3ELSLcqydc" + ], + "ui": { + "x": 558.985, + "y": 916.8385, + "zIndex": 634, + "widthName": 60, + "widthComment": 68, + "color": "#00BCD4" + }, + "meta": { + "updateAt": 1754465113941, + "createAt": 1754456958612 + } + }, + "yzPCT3ZlJYYiwy0SG_8oH": { + "id": "yzPCT3ZlJYYiwy0SG_8oH", + "name": "", + "comment": "샘플(NGS)", + "columnIds": [ + "fn9lrbfd6Gf1T8zGZZAPh", + "X0jjrE2xjhu2XAM_S5CcN" + ], + "seqColumnIds": [ + "fn9lrbfd6Gf1T8zGZZAPh", + "X0jjrE2xjhu2XAM_S5CcN" + ], + "ui": { + "x": 560.0159, + "y": 1130.2402, + "zIndex": 639, + "widthName": 60, + "widthComment": 60, + "color": "#00BCD4" + }, + "meta": { + "updateAt": 1754465150062, + "createAt": 1754456973064 + } + }, + "wARo-MUXMFDwfjCGKVOMl": { + "id": "wARo-MUXMFDwfjCGKVOMl", + "name": "", + "comment": "바코드(NGS)", + "columnIds": [ + "56oUBmwDHzCmSfAiwN754", + "m1zU36_YJ987r0tow4fcE" + ], + "seqColumnIds": [ + "56oUBmwDHzCmSfAiwN754", + "m1zU36_YJ987r0tow4fcE" + ], + "ui": { + "x": 548.6757, + "y": 1349.828, + "zIndex": 644, + "widthName": 60, + "widthComment": 69, + "color": "#00BCD4" + }, + "meta": { + "updateAt": 1754465153775, + "createAt": 1754456983772 + } + }, + "EfUkutYdpx5PS5QzCf67B": { + "id": "EfUkutYdpx5PS5QzCf67B", + "name": "", + "comment": "", + "columnIds": [], + "seqColumnIds": [], + "ui": { + "x": 256.80979381443296, + "y": 798.9687628865979, + "zIndex": 690, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1754459188797, + "createAt": 1754459188797 + } + }, + "-q6IWAb20IgbjrvUkuOFY": { + "id": "-q6IWAb20IgbjrvUkuOFY", + "name": "", + "comment": "파이프라인 마스터", + "columnIds": [ + "_QLhSunU16liy1P1UJKz1" + ], + "seqColumnIds": [ + "lwnV0oc_ATfSCSIjPBp2d", + "_QLhSunU16liy1P1UJKz1" + ], + "ui": { + "x": 549.5933, + "y": 1512.3708, + "zIndex": 690, + "widthName": 60, + "widthComment": 101, + "color": "" + }, + "meta": { + "updateAt": 1754459234119, + "createAt": 1754459205855 + } + }, + "kbP9od-gNNwXSElu7qaNa": { + "id": "kbP9od-gNNwXSElu7qaNa", + "name": "", + "comment": "시뮬레이션 툴 박스", + "columnIds": [ + "i2XwkQI8bl1_r2f7ZVnYr" + ], + "seqColumnIds": [ + "i2XwkQI8bl1_r2f7ZVnYr" + ], + "ui": { + "x": 106.462, + "y": 1656.1354, + "zIndex": 716, + "widthName": 60, + "widthComment": 105, + "color": "" + }, + "meta": { + "updateAt": 1754464824937, + "createAt": 1754459800220 + } + }, + "r5gRtDj458FLG5FFs-ny7": { + "id": "r5gRtDj458FLG5FFs-ny7", + "name": "", + "comment": "시뮬레이션 툴 박스 사용 이력", + "columnIds": [ + "dgLf0O6U1lIHaemcR5OcV" + ], + "seqColumnIds": [ + "dgLf0O6U1lIHaemcR5OcV" + ], + "ui": { + "x": 106.4621, + "y": 1873.4599, + "zIndex": 720, + "widthName": 60, + "widthComment": 159, + "color": "" + }, + "meta": { + "updateAt": 1754464829063, + "createAt": 1754459828912 + } + }, + "6IEGvqpssK0H42uHqPSNK": { + "id": "6IEGvqpssK0H42uHqPSNK", + "name": "", + "comment": "메일 로그", + "columnIds": [], + "seqColumnIds": [], + "ui": { + "x": 127.8351, + "y": 918.5566, + "zIndex": 726, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1754459874853, + "createAt": 1754459858948 + } + }, + "zP_GWMpvpT6wH7qn9vxdv": { + "id": "zP_GWMpvpT6wH7qn9vxdv", + "name": "", + "comment": "이벤트 로그", + "columnIds": [ + "eVoU1yjNQJOI-PIkl4URb" + ], + "seqColumnIds": [ + "eVoU1yjNQJOI-PIkl4URb" + ], + "ui": { + "x": 106.1853, + "y": 673.8832, + "zIndex": 739, + "widthName": 60, + "widthComment": 65, + "color": "" + }, + "meta": { + "updateAt": 1754460169389, + "createAt": 1754460011325 + } + }, + "Yw3xkCepE7qeZkn3zo3TY": { + "id": "Yw3xkCepE7qeZkn3zo3TY", + "name": "", + "comment": "NGS 작업 결과", + "columnIds": [ + "-zfxKNUMEPmIfXHgfQUEI", + "6OYGweXV0ot8K1DonjdOj" + ], + "seqColumnIds": [ + "-zfxKNUMEPmIfXHgfQUEI", + "6OYGweXV0ot8K1DonjdOj" + ], + "ui": { + "x": 1687.2255, + "y": 1228.1783, + "zIndex": 782, + "widthName": 60, + "widthComment": 80, + "color": "" + }, + "meta": { + "updateAt": 1754460710659, + "createAt": 1754460600233 + } + }, + "xGf94aIdnNFbMawDHNsnn": { + "id": "xGf94aIdnNFbMawDHNsnn", + "name": "", + "comment": "유전자", + "columnIds": [ + "byPDJPm_NdSCPxcJAcGXz" + ], + "seqColumnIds": [ + "7VU2lYLaGsl1fq2uL-PCi", + "byPDJPm_NdSCPxcJAcGXz" + ], + "ui": { + "x": 2409.2784, + "y": 237.1135, + "zIndex": 818, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1754462310415, + "createAt": 1754461911493 + } + }, + "_IBhvMR9bIYR0UYXq65hA": { + "id": "_IBhvMR9bIYR0UYXq65hA", + "name": "", + "comment": "유전자-Enzyme", + "columnIds": [], + "seqColumnIds": [], + "ui": { + "x": 2398.9691, + "y": 366.2562, + "zIndex": 824, + "widthName": 60, + "widthComment": 84, + "color": "" + }, + "meta": { + "updateAt": 1754523165348, + "createAt": 1754461923974 + } + }, + "kYx9YWvvOS89pMYnChmLJ": { + "id": "kYx9YWvvOS89pMYnChmLJ", + "name": "", + "comment": "Enzyme", + "columnIds": [], + "seqColumnIds": [], + "ui": { + "x": 2398.7805, + "y": 466.4322, + "zIndex": 832, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1754523173125, + "createAt": 1754461957622 + } + }, + "tcjXlQrjNpKf54GokA_l5": { + "id": "tcjXlQrjNpKf54GokA_l5", + "name": "", + "comment": "Enzyme_Reaction", + "columnIds": [], + "seqColumnIds": [ + "fNSv3NeEYjLH_Zjz-EuNO" + ], + "ui": { + "x": 2399.3463, + "y": 566.1427, + "zIndex": 840, + "widthName": 60, + "widthComment": 93, + "color": "" + }, + "meta": { + "updateAt": 1754523177466, + "createAt": 1754461975939 + } + }, + "xoqfDhL3amNw6Xb-_L0lq": { + "id": "xoqfDhL3amNw6Xb-_L0lq", + "name": "", + "comment": "Reaction ", + "columnIds": [], + "seqColumnIds": [ + "uwK2M_23DzY6kdy2WgIee" + ], + "ui": { + "x": 2397.4605, + "y": 663.8171, + "zIndex": 851, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1754523187957, + "createAt": 1754462021384 + } + }, + "665LiQRcz_kr-JL4DTwwB": { + "id": "665LiQRcz_kr-JL4DTwwB", + "name": "", + "comment": "Metabolite", + "columnIds": [], + "seqColumnIds": [], + "ui": { + "x": 2398.0261, + "y": 858.2096, + "zIndex": 856, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1754523218421, + "createAt": 1754462035744 + } + }, + "Bs8zWea7vJ_7NbzaxlAxU": { + "id": "Bs8zWea7vJ_7NbzaxlAxU", + "name": "", + "comment": "Reaction_Metabolite", + "columnIds": [], + "seqColumnIds": [], + "ui": { + "x": 2398.3026, + "y": 768.9084, + "zIndex": 862, + "widthName": 60, + "widthComment": 110, + "color": "" + }, + "meta": { + "updateAt": 1754523211349, + "createAt": 1754462045979 + } + }, + "BQS6494_Cs422SQSbfJ2n": { + "id": "BQS6494_Cs422SQSbfJ2n", + "name": "", + "comment": "화면 설명", + "columnIds": [], + "seqColumnIds": [ + "QATIJxGTzLt1kZ8bjk3gC" + ], + "ui": { + "x": 557.0083, + "y": 1665.8532, + "zIndex": 943, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1754465664566, + "createAt": 1754465643939 + } + }, + "fMdGmsjMSQ2cLtrNcE1gk": { + "id": "fMdGmsjMSQ2cLtrNcE1gk", + "name": "", + "comment": "배양 정보", + "columnIds": [ + "EI3awnEHwoSx-MFH1eeET" + ], + "seqColumnIds": [ + "3l2vZif9UhAa-4m-XaPk_", + "EI3awnEHwoSx-MFH1eeET" + ], + "ui": { + "x": 1215.8728, + "y": 1650, + "zIndex": 967, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1754527833723, + "createAt": 1754526161373 + } + }, + "P4rpGmH6RmTcoRD_sWJW4": { + "id": "P4rpGmH6RmTcoRD_sWJW4", + "name": "", + "comment": "배양 리포팅", + "columnIds": [ + "JN_ZE5P9VpDahCHy4E08q", + "n5lhDVesQxPdfub7uiz1E" + ], + "seqColumnIds": [ + "JN_ZE5P9VpDahCHy4E08q", + "n5lhDVesQxPdfub7uiz1E" + ], + "ui": { + "x": 1726.4452, + "y": 1638.2118, + "zIndex": 978, + "widthName": 60, + "widthComment": 65, + "color": "" + }, + "meta": { + "updateAt": 1754527830982, + "createAt": 1754527661347 + } + }, + "O6gR-_ZtKDlJpb0wAElPA": { + "id": "O6gR-_ZtKDlJpb0wAElPA", + "name": "", + "comment": "원부자재", + "columnIds": [ + "9mv7C3PejUCqVC7TOR17M" + ], + "seqColumnIds": [ + "9mv7C3PejUCqVC7TOR17M" + ], + "ui": { + "x": 1216.6894, + "y": 1891.8697, + "zIndex": 1021, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1754527857174, + "createAt": 1754527835341 + } + }, + "_EAn5ShGQYKY-ptV_vVFZ": { + "id": "_EAn5ShGQYKY-ptV_vVFZ", + "name": "", + "comment": "뉴가스 마이그레이션 테이블", + "columnIds": [], + "seqColumnIds": [], + "ui": { + "x": 1215.4699, + "y": 2044.3088, + "zIndex": 1028, + "widthName": 60, + "widthComment": 153, + "color": "#00BCD4" + }, + "meta": { + "updateAt": 1754527987630, + "createAt": 1754527964522 + } + }, + "jEo7qsqlX6uXALZ7P85iK": { + "id": "jEo7qsqlX6uXALZ7P85iK", + "name": "", + "comment": "균주 정보(발효 의뢰 시스템 연계)", + "columnIds": [], + "seqColumnIds": [], + "ui": { + "x": 104.8971, + "y": 2097.5607, + "zIndex": 1041, + "widthName": 60, + "widthComment": 178, + "color": "" + }, + "meta": { + "updateAt": 1754528097736, + "createAt": 1754528084515 + } + } + }, + "tableColumnEntities": { + "W4DdKpCeycWvaeeNXYOFt": { + "id": "W4DdKpCeycWvaeeNXYOFt", + "tableId": "GdxhuSk4fivseld2LfpQ_", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754381167323, + "createAt": 1754381167323 + } + }, + "Fl3HXhOKPtdW69eynS0yd": { + "id": "Fl3HXhOKPtdW69eynS0yd", + "tableId": "qhOl9wIjJqrVXh-bSv7ke", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754381177898, + "createAt": 1754381177898 + } + }, + "gOEACnjl8oIYmTLgFTsbh": { + "id": "gOEACnjl8oIYmTLgFTsbh", + "tableId": "GdxhuSk4fivseld2LfpQ_", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754381197193, + "createAt": 1754381197192 + } + }, + "0lyscs5BYOyHDM_xXE4At": { + "id": "0lyscs5BYOyHDM_xXE4At", + "tableId": "qhOl9wIjJqrVXh-bSv7ke", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754381199115, + "createAt": 1754381199114 + } + }, + "qhvoE6d1E7XPv_qZioXn9": { + "id": "qhvoE6d1E7XPv_qZioXn9", + "tableId": "-5qu8nMzz6rFtw4LgAYlu", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754382333923, + "createAt": 1754382333923 + } + }, + "mMcdNY5ZMyG7RWYIWrfCP": { + "id": "mMcdNY5ZMyG7RWYIWrfCP", + "tableId": "9U9lcHeB3nyao9FCFAdPC", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754382336029, + "createAt": 1754382336029 + } + }, + "_yLtTO_rw7k6cJYjfgK8m": { + "id": "_yLtTO_rw7k6cJYjfgK8m", + "tableId": "9U9lcHeB3nyao9FCFAdPC", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754382347068, + "createAt": 1754382347068 + } + }, + "Hp-tw27qQZE9E-_qygiiX": { + "id": "Hp-tw27qQZE9E-_qygiiX", + "tableId": "-5qu8nMzz6rFtw4LgAYlu", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754382348000, + "createAt": 1754382348000 + } + }, + "VtPDZvC98RevCb-BNU1I2": { + "id": "VtPDZvC98RevCb-BNU1I2", + "tableId": "ZECn5lpVsAoZptTLtCY3Q", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754437625882, + "createAt": 1754382435265 + } + }, + "Wl2wIBv5ZUdsDNDyqqms0": { + "id": "Wl2wIBv5ZUdsDNDyqqms0", + "tableId": "cga_ec1K1CMdP-kmiMOaU", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754382436276, + "createAt": 1754382436276 + } + }, + "-jlLf-KlnCypV1RSu3VL4": { + "id": "-jlLf-KlnCypV1RSu3VL4", + "tableId": "cga_ec1K1CMdP-kmiMOaU", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754382471330, + "createAt": 1754382471330 + } + }, + "VlovfgG2esD00MI11Q6tK": { + "id": "VlovfgG2esD00MI11Q6tK", + "tableId": "V2F5Frw7sCNzkLLdij-UM", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754382472620, + "createAt": 1754382472620 + } + }, + "4hqwsbU_xyBkx2utb4t3j": { + "id": "4hqwsbU_xyBkx2utb4t3j", + "tableId": "J0PRQVyskI51G4qHRo5S0", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754382552335, + "createAt": 1754382552335 + } + }, + "AqyFYfhQmIEV8wjjvCze_": { + "id": "AqyFYfhQmIEV8wjjvCze_", + "tableId": "J0PRQVyskI51G4qHRo5S0", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754437652255, + "createAt": 1754437652252 + } + }, + "jUn43teJpwa9tPgNJtzNw": { + "id": "jUn43teJpwa9tPgNJtzNw", + "tableId": "d9O9qp4Bp-WjuzThHIuxE", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754437653966, + "createAt": 1754437653965 + } + }, + "3E8-LrDjIaiAqWqeEvZ2C": { + "id": "3E8-LrDjIaiAqWqeEvZ2C", + "tableId": "d9O9qp4Bp-WjuzThHIuxE", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754437923590, + "createAt": 1754437923590 + } + }, + "Y5cGoKRT8kCo4xAECN3M8": { + "id": "Y5cGoKRT8kCo4xAECN3M8", + "tableId": "IkMA_k3YSf7oXRAgIqgWR", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754438115164, + "createAt": 1754438115164 + } + }, + "8LERJxERBdC27ggCEei2r": { + "id": "8LERJxERBdC27ggCEei2r", + "tableId": "IkMA_k3YSf7oXRAgIqgWR", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754438744615, + "createAt": 1754438744615 + } + }, + "568TVy-QhY4hkD5t6Blcn": { + "id": "568TVy-QhY4hkD5t6Blcn", + "tableId": "IkMA_k3YSf7oXRAgIqgWR", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754439305720, + "createAt": 1754439305720 + } + }, + "cn39aiZvY7EKbXdDNv8UJ": { + "id": "cn39aiZvY7EKbXdDNv8UJ", + "tableId": "DbdrGyXt-E-JbuoSHJ2iE", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754440093989, + "createAt": 1754440093989 + } + }, + "1bcrpFyPZVpvYGRKXZCc9": { + "id": "1bcrpFyPZVpvYGRKXZCc9", + "tableId": "DbdrGyXt-E-JbuoSHJ2iE", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754440239928, + "createAt": 1754440239928 + } + }, + "Zvo2LUN2HExSyxH4vLXUc": { + "id": "Zvo2LUN2HExSyxH4vLXUc", + "tableId": "himTd51e5v3kRUyGYe4NN", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754440240637, + "createAt": 1754440240637 + } + }, + "8HtDMkqi7YdcommwW4p8J": { + "id": "8HtDMkqi7YdcommwW4p8J", + "tableId": "DbdrGyXt-E-JbuoSHJ2iE", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754440685037, + "createAt": 1754440685036 + } + }, + "44a4KJr3AYVwaAHlgvTsQ": { + "id": "44a4KJr3AYVwaAHlgvTsQ", + "tableId": "qqI7n1cZsxqVUkosICW5E", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754440686015, + "createAt": 1754440686015 + } + }, + "8j7EyYMI81x9vyXb19mYx": { + "id": "8j7EyYMI81x9vyXb19mYx", + "tableId": "himTd51e5v3kRUyGYe4NN", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754440690991, + "createAt": 1754440690991 + } + }, + "UUt2XNO5QUbcMpXrVoa5L": { + "id": "UUt2XNO5QUbcMpXrVoa5L", + "tableId": "qqI7n1cZsxqVUkosICW5E", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754440692130, + "createAt": 1754440692130 + } + }, + "gVGl0fqaEpNpPcZIv5DFK": { + "id": "gVGl0fqaEpNpPcZIv5DFK", + "tableId": "Ii0qrARt-8gDVEMzVY7mM", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754443631694, + "createAt": 1754443631694 + } + }, + "A_WYW7xfYR-PnMjxumPc_": { + "id": "A_WYW7xfYR-PnMjxumPc_", + "tableId": "dMgB23-XyfyBrQtln8T6A", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754443632298, + "createAt": 1754443632298 + } + }, + "4QYIJ98XjTW50SND0UHEI": { + "id": "4QYIJ98XjTW50SND0UHEI", + "tableId": "dMgB23-XyfyBrQtln8T6A", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754443664224, + "createAt": 1754443664224 + } + }, + "o-AQaBMbBs0mLny2XcJ5p": { + "id": "o-AQaBMbBs0mLny2XcJ5p", + "tableId": "Ii0qrARt-8gDVEMzVY7mM", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754443665088, + "createAt": 1754443665088 + } + }, + "tqlJcNxMkT7ecK169-PIb": { + "id": "tqlJcNxMkT7ecK169-PIb", + "tableId": "J0PRQVyskI51G4qHRo5S0", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754444311193, + "createAt": 1754444311193 + } + }, + "d7sqF3TYGv3AW9uoqpr67": { + "id": "d7sqF3TYGv3AW9uoqpr67", + "tableId": "J0PRQVyskI51G4qHRo5S0", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754444549418, + "createAt": 1754444549418 + } + }, + "CSEVGwHrf8BH60Mzb3xYT": { + "id": "CSEVGwHrf8BH60Mzb3xYT", + "tableId": "dMgB23-XyfyBrQtln8T6A", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754444564906, + "createAt": 1754444564906 + } + }, + "MsFBx7x5Ad7B5Ey3Hm8bz": { + "id": "MsFBx7x5Ad7B5Ey3Hm8bz", + "tableId": "J0PRQVyskI51G4qHRo5S0", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754444705944, + "createAt": 1754444705944 + } + }, + "v62zqHVW5hq6fn1wU83kl": { + "id": "v62zqHVW5hq6fn1wU83kl", + "tableId": "J0PRQVyskI51G4qHRo5S0", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754444926864, + "createAt": 1754444926864 + } + }, + "WQM5_fqs1JPU5tMld5lkV": { + "id": "WQM5_fqs1JPU5tMld5lkV", + "tableId": "trYMd6wdMNDvKmaoWNXIx", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754445845140, + "createAt": 1754445845140 + } + }, + "AJ4VUft8Sd_MI-ze0I8O0": { + "id": "AJ4VUft8Sd_MI-ze0I8O0", + "tableId": "EQAGETeXaGT758Qm7scqi", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754453519589, + "createAt": 1754453518535 + } + }, + "6wZVzAGVd1XHSDSy4C9j5": { + "id": "6wZVzAGVd1XHSDSy4C9j5", + "tableId": "EQAGETeXaGT758Qm7scqi", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754453572803, + "createAt": 1754453572803 + } + }, + "WTuECNDiy8M-4TbJkAMZr": { + "id": "WTuECNDiy8M-4TbJkAMZr", + "tableId": "Ii0qrARt-8gDVEMzVY7mM", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754453573938, + "createAt": 1754453573938 + } + }, + "DVHJQN88K3BepltoeGPDP": { + "id": "DVHJQN88K3BepltoeGPDP", + "tableId": "cnbWJvE8hHTzCVJCwH6na", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754453971129, + "createAt": 1754453971129 + } + }, + "Sp78cophNBj4x_2PStbBx": { + "id": "Sp78cophNBj4x_2PStbBx", + "tableId": "DFKh-HfnYWxi11eZCqkgK", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754453971850, + "createAt": 1754453971850 + } + }, + "7KudidjQAZzTzaUZVF6iZ": { + "id": "7KudidjQAZzTzaUZVF6iZ", + "tableId": "DFKh-HfnYWxi11eZCqkgK", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754453981611, + "createAt": 1754453981610 + } + }, + "Zod_8Knm5VO7zg5tahBP4": { + "id": "Zod_8Knm5VO7zg5tahBP4", + "tableId": "_6YaIHBhh5zFfzCmAXN92", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754453999492, + "createAt": 1754453999491 + } + }, + "sqasH1nnOalUWKNdS4p7X": { + "id": "sqasH1nnOalUWKNdS4p7X", + "tableId": "Xq4nUH16urgOVDvzpQpiX", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754454001567, + "createAt": 1754454001567 + } + }, + "vRXMqti-9v-ydmvKX8Ht7": { + "id": "vRXMqti-9v-ydmvKX8Ht7", + "tableId": "7_V9K4Pn6pwpgUiJ7FT-V", + "name": "상태", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754456769484, + "createAt": 1754454074012 + } + }, + "9SPqiG6vkNx8IX8s95-Sa": { + "id": "9SPqiG6vkNx8IX8s95-Sa", + "tableId": "DFKh-HfnYWxi11eZCqkgK", + "name": "바코드", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754456592167, + "createAt": 1754455339772 + } + }, + "998hQJ57qiQvk3rpO5soW": { + "id": "998hQJ57qiQvk3rpO5soW", + "tableId": "DFKh-HfnYWxi11eZCqkgK", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754456732993, + "createAt": 1754456732993 + } + }, + "er5BYXkeByO3tF7TaelR-": { + "id": "er5BYXkeByO3tF7TaelR-", + "tableId": "7_V9K4Pn6pwpgUiJ7FT-V", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754456734011, + "createAt": 1754456734010 + } + }, + "urO6c-0U2Cf3ELSLcqydc": { + "id": "urO6c-0U2Cf3ELSLcqydc", + "tableId": "ZwKFlfp-W6ARlfved6xt2", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754457011017, + "createAt": 1754457011017 + } + }, + "fn9lrbfd6Gf1T8zGZZAPh": { + "id": "fn9lrbfd6Gf1T8zGZZAPh", + "tableId": "yzPCT3ZlJYYiwy0SG_8oH", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754457011539, + "createAt": 1754457011539 + } + }, + "X0jjrE2xjhu2XAM_S5CcN": { + "id": "X0jjrE2xjhu2XAM_S5CcN", + "tableId": "yzPCT3ZlJYYiwy0SG_8oH", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754457014168, + "createAt": 1754457014168 + } + }, + "56oUBmwDHzCmSfAiwN754": { + "id": "56oUBmwDHzCmSfAiwN754", + "tableId": "wARo-MUXMFDwfjCGKVOMl", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754457014555, + "createAt": 1754457014555 + } + }, + "m1zU36_YJ987r0tow4fcE": { + "id": "m1zU36_YJ987r0tow4fcE", + "tableId": "wARo-MUXMFDwfjCGKVOMl", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754457066899, + "createAt": 1754457066899 + } + }, + "qQgNRyMv4DeRjGHkWsbKQ": { + "id": "qQgNRyMv4DeRjGHkWsbKQ", + "tableId": "DFKh-HfnYWxi11eZCqkgK", + "name": "바코드", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754457074103, + "createAt": 1754457067595 + } + }, + "lwnV0oc_ATfSCSIjPBp2d": { + "id": "lwnV0oc_ATfSCSIjPBp2d", + "tableId": "-q6IWAb20IgbjrvUkuOFY", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754459221307, + "createAt": 1754459221307 + } + }, + "_QLhSunU16liy1P1UJKz1": { + "id": "_QLhSunU16liy1P1UJKz1", + "tableId": "-q6IWAb20IgbjrvUkuOFY", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754459234119, + "createAt": 1754459234119 + } + }, + "Qni8gmYzxmgRQpm92u3jV": { + "id": "Qni8gmYzxmgRQpm92u3jV", + "tableId": "DFKh-HfnYWxi11eZCqkgK", + "name": "파이프라인", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 62, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754459244885, + "createAt": 1754459235069 + } + }, + "tUSO7tRNXuublZhwP2cBW": { + "id": "tUSO7tRNXuublZhwP2cBW", + "tableId": "dMgB23-XyfyBrQtln8T6A", + "name": "유형", + "comment": "신규/외부", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754459555826, + "createAt": 1754459545935 + } + }, + "YkQGVKARv46Tmp7L2zRCP": { + "id": "YkQGVKARv46Tmp7L2zRCP", + "tableId": "dMgB23-XyfyBrQtln8T6A", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754459550288, + "createAt": 1754459550288 + } + }, + "i2XwkQI8bl1_r2f7ZVnYr": { + "id": "i2XwkQI8bl1_r2f7ZVnYr", + "tableId": "kbP9od-gNNwXSElu7qaNa", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754459890070, + "createAt": 1754459890070 + } + }, + "dgLf0O6U1lIHaemcR5OcV": { + "id": "dgLf0O6U1lIHaemcR5OcV", + "tableId": "r5gRtDj458FLG5FFs-ny7", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754459890423, + "createAt": 1754459890423 + } + }, + "eVoU1yjNQJOI-PIkl4URb": { + "id": "eVoU1yjNQJOI-PIkl4URb", + "tableId": "zP_GWMpvpT6wH7qn9vxdv", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754460105807, + "createAt": 1754460105806 + } + }, + "Xqf75OtW1XCwmT8ZaOulD": { + "id": "Xqf75OtW1XCwmT8ZaOulD", + "tableId": "DFKh-HfnYWxi11eZCqkgK", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754460568111, + "createAt": 1754460568111 + } + }, + "MYB8w1R7Zwiykn_daUJka": { + "id": "MYB8w1R7Zwiykn_daUJka", + "tableId": "7_V9K4Pn6pwpgUiJ7FT-V", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754460569184, + "createAt": 1754460569184 + } + }, + "-zfxKNUMEPmIfXHgfQUEI": { + "id": "-zfxKNUMEPmIfXHgfQUEI", + "tableId": "Yw3xkCepE7qeZkn3zo3TY", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754460673600, + "createAt": 1754460673600 + } + }, + "6OYGweXV0ot8K1DonjdOj": { + "id": "6OYGweXV0ot8K1DonjdOj", + "tableId": "Yw3xkCepE7qeZkn3zo3TY", + "name": "타입", + "comment": "균총 등", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754460699313, + "createAt": 1754460680454 + } + }, + "uwK2M_23DzY6kdy2WgIee": { + "id": "uwK2M_23DzY6kdy2WgIee", + "tableId": "xoqfDhL3amNw6Xb-_L0lq", + "name": "Reaction", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754462021901, + "createAt": 1754462021900 + } + }, + "fNSv3NeEYjLH_Zjz-EuNO": { + "id": "fNSv3NeEYjLH_Zjz-EuNO", + "tableId": "tcjXlQrjNpKf54GokA_l5", + "name": "Metabolite", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754462043265, + "createAt": 1754462043264 + } + }, + "7VU2lYLaGsl1fq2uL-PCi": { + "id": "7VU2lYLaGsl1fq2uL-PCi", + "tableId": "xGf94aIdnNFbMawDHNsnn", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754462285812, + "createAt": 1754462285812 + } + }, + "aCkS4Nc3SibCy7Y-AdeVg": { + "id": "aCkS4Nc3SibCy7Y-AdeVg", + "tableId": "dMgB23-XyfyBrQtln8T6A", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754462286448, + "createAt": 1754462286448 + } + }, + "byPDJPm_NdSCPxcJAcGXz": { + "id": "byPDJPm_NdSCPxcJAcGXz", + "tableId": "xGf94aIdnNFbMawDHNsnn", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754462308698, + "createAt": 1754462308698 + } + }, + "QATIJxGTzLt1kZ8bjk3gC": { + "id": "QATIJxGTzLt1kZ8bjk3gC", + "tableId": "BQS6494_Cs422SQSbfJ2n", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754465663354, + "createAt": 1754465655212 + } + }, + "JN_ZE5P9VpDahCHy4E08q": { + "id": "JN_ZE5P9VpDahCHy4E08q", + "tableId": "P4rpGmH6RmTcoRD_sWJW4", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754527683130, + "createAt": 1754527683129 + } + }, + "3l2vZif9UhAa-4m-XaPk_": { + "id": "3l2vZif9UhAa-4m-XaPk_", + "tableId": "fMdGmsjMSQ2cLtrNcE1gk", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754527683467, + "createAt": 1754527683467 + } + }, + "EI3awnEHwoSx-MFH1eeET": { + "id": "EI3awnEHwoSx-MFH1eeET", + "tableId": "fMdGmsjMSQ2cLtrNcE1gk", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754527691952, + "createAt": 1754527691952 + } + }, + "n5lhDVesQxPdfub7uiz1E": { + "id": "n5lhDVesQxPdfub7uiz1E", + "tableId": "P4rpGmH6RmTcoRD_sWJW4", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754527692448, + "createAt": 1754527692448 + } + }, + "9mv7C3PejUCqVC7TOR17M": { + "id": "9mv7C3PejUCqVC7TOR17M", + "tableId": "O6gR-_ZtKDlJpb0wAElPA", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1754527854297, + "createAt": 1754527854297 + } + } + }, + "relationshipEntities": { + "us70cNHBOxMZ7hbqFJ6bG": { + "id": "us70cNHBOxMZ7hbqFJ6bG", + "identification": false, + "relationshipType": 2, + "startRelationshipType": 2, + "start": { + "tableId": "GdxhuSk4fivseld2LfpQ_", + "columnIds": [ + "gOEACnjl8oIYmTLgFTsbh" + ], + "x": 458, + "y": 323, + "direction": 2 + }, + "end": { + "tableId": "qhOl9wIjJqrVXh-bSv7ke", + "columnIds": [ + "0lyscs5BYOyHDM_xXE4At" + ], + "x": 721, + "y": 312, + "direction": 1 + }, + "meta": { + "updateAt": 1754381199115, + "createAt": 1754381199115 + } + }, + "AHpH7pSY1rtTRmuAvYyeu": { + "id": "AHpH7pSY1rtTRmuAvYyeu", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "-5qu8nMzz6rFtw4LgAYlu", + "columnIds": [ + "qhvoE6d1E7XPv_qZioXn9" + ], + "x": 528, + "y": 131, + "direction": 2 + }, + "end": { + "tableId": "9U9lcHeB3nyao9FCFAdPC", + "columnIds": [ + "mMcdNY5ZMyG7RWYIWrfCP" + ], + "x": 995, + "y": 140, + "direction": 1 + }, + "meta": { + "updateAt": 1754382336029, + "createAt": 1754382336029 + } + }, + "ZcMO2M-zi0H67MLvkQFeA": { + "id": "ZcMO2M-zi0H67MLvkQFeA", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "9U9lcHeB3nyao9FCFAdPC", + "columnIds": [ + "_yLtTO_rw7k6cJYjfgK8m" + ], + "x": 1269.8658, + "y": 94.43299999999999, + "direction": 1 + }, + "end": { + "tableId": "-5qu8nMzz6rFtw4LgAYlu", + "columnIds": [ + "Hp-tw27qQZE9E-_qygiiX" + ], + "x": 1012, + "y": 96, + "direction": 2 + }, + "meta": { + "updateAt": 1754382348000, + "createAt": 1754382348000 + } + }, + "qA2OY8Vv0tB0-YE4YpkJC": { + "id": "qA2OY8Vv0tB0-YE4YpkJC", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "ZECn5lpVsAoZptTLtCY3Q", + "columnIds": [ + "VtPDZvC98RevCb-BNU1I2" + ], + "x": 1624.5, + "y": 315.9691, + "direction": 8 + }, + "end": { + "tableId": "cga_ec1K1CMdP-kmiMOaU", + "columnIds": [ + "Wl2wIBv5ZUdsDNDyqqms0" + ], + "x": 1623.5, + "y": 536, + "direction": 4 + }, + "meta": { + "updateAt": 1754382436276, + "createAt": 1754382436276 + } + }, + "9Hb7Dk-7X8s_d8BxRf8sy": { + "id": "9Hb7Dk-7X8s_d8BxRf8sy", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "cga_ec1K1CMdP-kmiMOaU", + "columnIds": [ + "-jlLf-KlnCypV1RSu3VL4" + ], + "x": 1441, + "y": 562, + "direction": 1 + }, + "end": { + "tableId": "V2F5Frw7sCNzkLLdij-UM", + "columnIds": [ + "VlovfgG2esD00MI11Q6tK" + ], + "x": 1345.1322, + "y": 561.902, + "direction": 2 + }, + "meta": { + "updateAt": 1754382472620, + "createAt": 1754382472620 + } + }, + "ofL59eVcur6yII49QRRVW": { + "id": "ofL59eVcur6yII49QRRVW", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "cga_ec1K1CMdP-kmiMOaU", + "columnIds": [ + "-jlLf-KlnCypV1RSu3VL4" + ], + "x": 1441, + "y": 588, + "direction": 1 + }, + "end": { + "tableId": "J0PRQVyskI51G4qHRo5S0", + "columnIds": [ + "4hqwsbU_xyBkx2utb4t3j" + ], + "x": 1273, + "y": 605, + "direction": 2 + }, + "meta": { + "updateAt": 1754382552335, + "createAt": 1754382552335 + } + }, + "an468nXDJltDt-CVHJjbM": { + "id": "an468nXDJltDt-CVHJjbM", + "identification": false, + "relationshipType": 2, + "startRelationshipType": 2, + "start": { + "tableId": "J0PRQVyskI51G4qHRo5S0", + "columnIds": [ + "AqyFYfhQmIEV8wjjvCze_" + ], + "x": 624.5, + "y": 683, + "direction": 8 + }, + "end": { + "tableId": "d9O9qp4Bp-WjuzThHIuxE", + "columnIds": [ + "jUn43teJpwa9tPgNJtzNw" + ], + "x": 625.5, + "y": 791.6666, + "direction": 4 + }, + "meta": { + "updateAt": 1754437653967, + "createAt": 1754437653967 + } + }, + "lBM6uJcLX1w5yGduCTtBc": { + "id": "lBM6uJcLX1w5yGduCTtBc", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "J0PRQVyskI51G4qHRo5S0", + "columnIds": [ + "AqyFYfhQmIEV8wjjvCze_" + ], + "x": 1165.7577, + "y": 772.1756, + "direction": 8 + }, + "end": { + "tableId": "d9O9qp4Bp-WjuzThHIuxE", + "columnIds": [ + "3E8-LrDjIaiAqWqeEvZ2C" + ], + "x": 1165.3235, + "y": 872.7044, + "direction": 4 + }, + "meta": { + "updateAt": 1754437923590, + "createAt": 1754437923590 + } + }, + "LFS3ebvouJc04nEFQAN8V": { + "id": "LFS3ebvouJc04nEFQAN8V", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "-5qu8nMzz6rFtw4LgAYlu", + "columnIds": [ + "qhvoE6d1E7XPv_qZioXn9" + ], + "x": 625.5, + "y": 191, + "direction": 8 + }, + "end": { + "tableId": "IkMA_k3YSf7oXRAgIqgWR", + "columnIds": [ + "Y5cGoKRT8kCo4xAECN3M8" + ], + "x": 623.5156, + "y": 329.6664, + "direction": 4 + }, + "meta": { + "updateAt": 1754438115164, + "createAt": 1754438115164 + } + }, + "RK0Atmk9dGVKv06yDhVUH": { + "id": "RK0Atmk9dGVKv06yDhVUH", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "-5qu8nMzz6rFtw4LgAYlu", + "columnIds": [ + "qhvoE6d1E7XPv_qZioXn9" + ], + "x": 829.5, + "y": 148, + "direction": 8 + }, + "end": { + "tableId": "IkMA_k3YSf7oXRAgIqgWR", + "columnIds": [ + "8LERJxERBdC27ggCEei2r" + ], + "x": 757.907, + "y": 250.0684, + "direction": 4 + }, + "meta": { + "updateAt": 1754438744615, + "createAt": 1754438744615 + } + }, + "5DBcuhyhho44sWllgvAKo": { + "id": "5DBcuhyhho44sWllgvAKo", + "identification": false, + "relationshipType": 2, + "startRelationshipType": 2, + "start": { + "tableId": "J0PRQVyskI51G4qHRo5S0", + "columnIds": [ + "AqyFYfhQmIEV8wjjvCze_" + ], + "x": 1090.5, + "y": 529, + "direction": 4 + }, + "end": { + "tableId": "IkMA_k3YSf7oXRAgIqgWR", + "columnIds": [ + "568TVy-QhY4hkD5t6Blcn" + ], + "x": 1010.5104, + "y": 388.2953, + "direction": 2 + }, + "meta": { + "updateAt": 1754439305720, + "createAt": 1754439305720 + } + }, + "IY-Yho5L0plKCcbyO8BwY": { + "id": "IY-Yho5L0plKCcbyO8BwY", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "-5qu8nMzz6rFtw4LgAYlu", + "columnIds": [ + "qhvoE6d1E7XPv_qZioXn9" + ], + "x": 647, + "y": 96, + "direction": 1 + }, + "end": { + "tableId": "DbdrGyXt-E-JbuoSHJ2iE", + "columnIds": [ + "cn39aiZvY7EKbXdDNv8UJ" + ], + "x": 471.8455, + "y": 108.3709, + "direction": 2 + }, + "meta": { + "updateAt": 1754440439776, + "createAt": 1754440093990 + } + }, + "otHQ_i4Dlzu0CfID5I0rs": { + "id": "otHQ_i4Dlzu0CfID5I0rs", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "DbdrGyXt-E-JbuoSHJ2iE", + "columnIds": [ + "1bcrpFyPZVpvYGRKXZCc9" + ], + "x": 294.5, + "y": 148, + "direction": 8 + }, + "end": { + "tableId": "himTd51e5v3kRUyGYe4NN", + "columnIds": [ + "Zvo2LUN2HExSyxH4vLXUc" + ], + "x": 300.5, + "y": 207, + "direction": 4 + }, + "meta": { + "updateAt": 1754440415807, + "createAt": 1754440240638 + } + }, + "vLKcVXyuGv1IfCGVIOB8e": { + "id": "vLKcVXyuGv1IfCGVIOB8e", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "DbdrGyXt-E-JbuoSHJ2iE", + "columnIds": [ + "8HtDMkqi7YdcommwW4p8J" + ], + "x": 289.3455, + "y": 160.3709, + "direction": 8 + }, + "end": { + "tableId": "qqI7n1cZsxqVUkosICW5E", + "columnIds": [ + "44a4KJr3AYVwaAHlgvTsQ" + ], + "x": 289.71590000000003, + "y": 259.7933, + "direction": 4 + }, + "meta": { + "updateAt": 1754440686016, + "createAt": 1754440686016 + } + }, + "vdEfrV3BTtVAnn8Q73CDu": { + "id": "vdEfrV3BTtVAnn8Q73CDu", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "himTd51e5v3kRUyGYe4NN", + "columnIds": [ + "8j7EyYMI81x9vyXb19mYx" + ], + "x": 281.9433, + "y": 472.9795, + "direction": 4 + }, + "end": { + "tableId": "qqI7n1cZsxqVUkosICW5E", + "columnIds": [ + "UUt2XNO5QUbcMpXrVoa5L" + ], + "x": 289.71590000000003, + "y": 363.7933, + "direction": 8 + }, + "meta": { + "updateAt": 1754440692130, + "createAt": 1754440692130 + } + }, + "g49h52atXgySPSSQWww7f": { + "id": "g49h52atXgySPSSQWww7f", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "Ii0qrARt-8gDVEMzVY7mM", + "columnIds": [ + "gVGl0fqaEpNpPcZIv5DFK" + ], + "x": 3012.256, + "y": 250.5154, + "direction": 4 + }, + "end": { + "tableId": "dMgB23-XyfyBrQtln8T6A", + "columnIds": [ + "A_WYW7xfYR-PnMjxumPc_" + ], + "x": 3019.4724, + "y": 126.3916, + "direction": 8 + }, + "meta": { + "updateAt": 1754443632299, + "createAt": 1754443632299 + } + }, + "mg8Oa2iZBBcbMav9Olcuw": { + "id": "mg8Oa2iZBBcbMav9Olcuw", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "dMgB23-XyfyBrQtln8T6A", + "columnIds": [ + "4QYIJ98XjTW50SND0UHEI" + ], + "x": 291.6373, + "y": 627.4225, + "direction": 8 + }, + "end": { + "tableId": "Ii0qrARt-8gDVEMzVY7mM", + "columnIds": [ + "o-AQaBMbBs0mLny2XcJ5p" + ], + "x": 292.6683, + "y": 1020.6186, + "direction": 4 + }, + "meta": { + "updateAt": 1754443750001, + "createAt": 1754443665088 + } + }, + "N-o-c6iAlhvUaMDc33YZe": { + "id": "N-o-c6iAlhvUaMDc33YZe", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "dMgB23-XyfyBrQtln8T6A", + "columnIds": [ + "4QYIJ98XjTW50SND0UHEI" + ], + "x": 470.0137, + "y": 751.34, + "direction": 2 + }, + "end": { + "tableId": "J0PRQVyskI51G4qHRo5S0", + "columnIds": [ + "tqlJcNxMkT7ecK169-PIb" + ], + "x": 908, + "y": 625, + "direction": 1 + }, + "meta": { + "updateAt": 1754444311193, + "createAt": 1754444311193 + } + }, + "ADvmAUT3MvIHwS1MK1ZFh": { + "id": "ADvmAUT3MvIHwS1MK1ZFh", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "dMgB23-XyfyBrQtln8T6A", + "columnIds": [ + "4QYIJ98XjTW50SND0UHEI" + ], + "x": 470.0137, + "y": 751.34, + "direction": 2 + }, + "end": { + "tableId": "J0PRQVyskI51G4qHRo5S0", + "columnIds": [ + "d7sqF3TYGv3AW9uoqpr67" + ], + "x": 908, + "y": 605, + "direction": 1 + }, + "meta": { + "updateAt": 1754444549418, + "createAt": 1754444549418 + } + }, + "64stQ2SFYsVzn-Im8IPpn": { + "id": "64stQ2SFYsVzn-Im8IPpn", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "dMgB23-XyfyBrQtln8T6A", + "columnIds": [ + "4QYIJ98XjTW50SND0UHEI" + ], + "x": 462.3849, + "y": 749.4843, + "direction": 4 + }, + "end": { + "tableId": "dMgB23-XyfyBrQtln8T6A", + "columnIds": [ + "CSEVGwHrf8BH60Mzb3xYT" + ], + "x": 482.3849, + "y": 769.4843, + "direction": 2 + }, + "meta": { + "updateAt": 1754444564906, + "createAt": 1754444564906 + } + }, + "UhArtDWdx6IjXzb8LLO4G": { + "id": "UhArtDWdx6IjXzb8LLO4G", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "cga_ec1K1CMdP-kmiMOaU", + "columnIds": [ + "-jlLf-KlnCypV1RSu3VL4" + ], + "x": 1441, + "y": 614, + "direction": 1 + }, + "end": { + "tableId": "J0PRQVyskI51G4qHRo5S0", + "columnIds": [ + "MsFBx7x5Ad7B5Ey3Hm8bz" + ], + "x": 1348.2577, + "y": 720.1756, + "direction": 2 + }, + "meta": { + "updateAt": 1754444705944, + "createAt": 1754444705944 + } + }, + "UEGyl82wMT2wL76cvWKTb": { + "id": "UEGyl82wMT2wL76cvWKTb", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "dMgB23-XyfyBrQtln8T6A", + "columnIds": [ + "4QYIJ98XjTW50SND0UHEI" + ], + "x": 468.9828, + "y": 739.9999, + "direction": 2 + }, + "end": { + "tableId": "J0PRQVyskI51G4qHRo5S0", + "columnIds": [ + "v62zqHVW5hq6fn1wU83kl" + ], + "x": 900.7835, + "y": 601.2475, + "direction": 1 + }, + "meta": { + "updateAt": 1754444926864, + "createAt": 1754444926864 + } + }, + "C-o6_l-NoeEaj7C9H8Mp6": { + "id": "C-o6_l-NoeEaj7C9H8Mp6", + "identification": false, + "relationshipType": 2, + "startRelationshipType": 2, + "start": { + "tableId": "dMgB23-XyfyBrQtln8T6A", + "columnIds": [ + "4QYIJ98XjTW50SND0UHEI" + ], + "x": 2400.8898, + "y": 95.2988, + "direction": 1 + }, + "end": { + "tableId": "trYMd6wdMNDvKmaoWNXIx", + "columnIds": [ + "WQM5_fqs1JPU5tMld5lkV" + ], + "x": 2267.0617, + "y": 94.6389, + "direction": 2 + }, + "meta": { + "updateAt": 1754445845140, + "createAt": 1754445845140 + } + }, + "sJsxGEyjjZY4WmbJW9z6x": { + "id": "sJsxGEyjjZY4WmbJW9z6x", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "EQAGETeXaGT758Qm7scqi", + "columnIds": [ + "6wZVzAGVd1XHSDSy4C9j5" + ], + "x": 297.1548, + "y": 1217.1129, + "direction": 8 + }, + "end": { + "tableId": "Ii0qrARt-8gDVEMzVY7mM", + "columnIds": [ + "WTuECNDiy8M-4TbJkAMZr" + ], + "x": 299.8848, + "y": 1334.0203, + "direction": 4 + }, + "meta": { + "updateAt": 1754453573938, + "createAt": 1754453573938 + } + }, + "MpzKOIUeUlnNt3sxOZlmN": { + "id": "MpzKOIUeUlnNt3sxOZlmN", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "cnbWJvE8hHTzCVJCwH6na", + "columnIds": [ + "DVHJQN88K3BepltoeGPDP" + ], + "x": 1198.1603, + "y": 938.2815, + "direction": 1 + }, + "end": { + "tableId": "DFKh-HfnYWxi11eZCqkgK", + "columnIds": [ + "Sp78cophNBj4x_2PStbBx" + ], + "x": 1137.4074, + "y": 1164.7283, + "direction": 2 + }, + "meta": { + "updateAt": 1754453971850, + "createAt": 1754453971850 + } + }, + "nNXynoaZirFpqYagY9Pp1": { + "id": "nNXynoaZirFpqYagY9Pp1", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "cnbWJvE8hHTzCVJCwH6na", + "columnIds": [ + "DVHJQN88K3BepltoeGPDP" + ], + "x": 1419.0185, + "y": 1062.9305, + "direction": 8 + }, + "end": { + "tableId": "DFKh-HfnYWxi11eZCqkgK", + "columnIds": [ + "7KudidjQAZzTzaUZVF6iZ" + ], + "x": 1405.3926, + "y": 1240.192, + "direction": 4 + }, + "meta": { + "updateAt": 1754453981611, + "createAt": 1754453981611 + } + }, + "DBiryV8_l2qPzyD24jDwA": { + "id": "DBiryV8_l2qPzyD24jDwA", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "cnbWJvE8hHTzCVJCwH6na", + "columnIds": [ + "DVHJQN88K3BepltoeGPDP" + ], + "x": 1473.4697, + "y": 1022.1988, + "direction": 2 + }, + "end": { + "tableId": "_6YaIHBhh5zFfzCmAXN92", + "columnIds": [ + "Zod_8Knm5VO7zg5tahBP4" + ], + "x": 1644.3297, + "y": 1137.9379, + "direction": 1 + }, + "meta": { + "updateAt": 1754453999492, + "createAt": 1754453999492 + } + }, + "6C8MKeulqejAgUxqYPsOx": { + "id": "6C8MKeulqejAgUxqYPsOx", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "cnbWJvE8hHTzCVJCwH6na", + "columnIds": [ + "DVHJQN88K3BepltoeGPDP" + ], + "x": 1601.5185, + "y": 1022.9305, + "direction": 2 + }, + "end": { + "tableId": "Xq4nUH16urgOVDvzpQpiX", + "columnIds": [ + "sqasH1nnOalUWKNdS4p7X" + ], + "x": 1645.361, + "y": 878.144, + "direction": 1 + }, + "meta": { + "updateAt": 1754454001567, + "createAt": 1754454001567 + } + }, + "NgfvhXeiOdXR-LRFSpDfk": { + "id": "NgfvhXeiOdXR-LRFSpDfk", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "cnbWJvE8hHTzCVJCwH6na", + "columnIds": [ + "DVHJQN88K3BepltoeGPDP" + ], + "x": 1454.913, + "y": 931.9582666666665, + "direction": 2 + }, + "end": { + "tableId": "7_V9K4Pn6pwpgUiJ7FT-V", + "columnIds": [ + "vRXMqti-9v-ydmvKX8Ht7" + ], + "x": 1645.583, + "y": 1055.8074000000001, + "direction": 1 + }, + "meta": { + "updateAt": 1754454074012, + "createAt": 1754454074012 + } + }, + "19L38XhKlp_VB7nhriKv2": { + "id": "19L38XhKlp_VB7nhriKv2", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "EQAGETeXaGT758Qm7scqi", + "columnIds": [ + "6wZVzAGVd1XHSDSy4C9j5" + ], + "x": 936.3558, + "y": 1134.8449, + "direction": 2 + }, + "end": { + "tableId": "DFKh-HfnYWxi11eZCqkgK", + "columnIds": [ + "9SPqiG6vkNx8IX8s95-Sa" + ], + "x": 1086.8407, + "y": 1144.7696, + "direction": 1 + }, + "meta": { + "updateAt": 1754455339772, + "createAt": 1754455339772 + } + }, + "DGgtbSDUIIe1yIwT621XT": { + "id": "DGgtbSDUIIe1yIwT621XT", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "DFKh-HfnYWxi11eZCqkgK", + "columnIds": [ + "998hQJ57qiQvk3rpO5soW" + ], + "x": 1288.8977, + "y": 1355.0786, + "direction": 8 + }, + "end": { + "tableId": "7_V9K4Pn6pwpgUiJ7FT-V", + "columnIds": [ + "er5BYXkeByO3tF7TaelR-" + ], + "x": 1287.8766, + "y": 1444.6733, + "direction": 4 + }, + "meta": { + "updateAt": 1754456734011, + "createAt": 1754456734011 + } + }, + "QJtEubwRLcmguXwd7VD6y": { + "id": "QJtEubwRLcmguXwd7VD6y", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "ZwKFlfp-W6ARlfved6xt2", + "columnIds": [ + "urO6c-0U2Cf3ELSLcqydc" + ], + "x": 741.485, + "y": 996.8385, + "direction": 8 + }, + "end": { + "tableId": "yzPCT3ZlJYYiwy0SG_8oH", + "columnIds": [ + "fn9lrbfd6Gf1T8zGZZAPh" + ], + "x": 742.5159, + "y": 1130.2402, + "direction": 4 + }, + "meta": { + "updateAt": 1754465181088, + "createAt": 1754457011539 + } + }, + "sJyqru4WOO84I83NayBmj": { + "id": "sJyqru4WOO84I83NayBmj", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "yzPCT3ZlJYYiwy0SG_8oH", + "columnIds": [ + "X0jjrE2xjhu2XAM_S5CcN" + ], + "x": 742.5159, + "y": 1234.2402, + "direction": 8 + }, + "end": { + "tableId": "wARo-MUXMFDwfjCGKVOMl", + "columnIds": [ + "56oUBmwDHzCmSfAiwN754" + ], + "x": 731.1757, + "y": 1349.828, + "direction": 4 + }, + "meta": { + "updateAt": 1754457014555, + "createAt": 1754457014555 + } + }, + "HBFcb8g6icKs1QYEYfKGu": { + "id": "HBFcb8g6icKs1QYEYfKGu", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "wARo-MUXMFDwfjCGKVOMl", + "columnIds": [ + "m1zU36_YJ987r0tow4fcE" + ], + "x": 913.6757, + "y": 1401.828, + "direction": 2 + }, + "end": { + "tableId": "DFKh-HfnYWxi11eZCqkgK", + "columnIds": [ + "qQgNRyMv4DeRjGHkWsbKQ" + ], + "x": 1221.8926, + "y": 1278.192, + "direction": 1 + }, + "meta": { + "updateAt": 1754457067595, + "createAt": 1754457067595 + } + }, + "tmg5QWYb-Lx9oVhjFj8v9": { + "id": "tmg5QWYb-Lx9oVhjFj8v9", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "-q6IWAb20IgbjrvUkuOFY", + "columnIds": [ + "_QLhSunU16liy1P1UJKz1" + ], + "x": 914.5933, + "y": 1552.3708, + "direction": 2 + }, + "end": { + "tableId": "DFKh-HfnYWxi11eZCqkgK", + "columnIds": [ + "Qni8gmYzxmgRQpm92u3jV" + ], + "x": 1221.8926, + "y": 1354.192, + "direction": 1 + }, + "meta": { + "updateAt": 1754459235069, + "createAt": 1754459235069 + } + }, + "g48QHmvkdOuciC6Bk_q0o": { + "id": "g48QHmvkdOuciC6Bk_q0o", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "kbP9od-gNNwXSElu7qaNa", + "columnIds": [ + "i2XwkQI8bl1_r2f7ZVnYr" + ], + "x": 288.962, + "y": 1736.1354, + "direction": 8 + }, + "end": { + "tableId": "r5gRtDj458FLG5FFs-ny7", + "columnIds": [ + "dgLf0O6U1lIHaemcR5OcV" + ], + "x": 288.9621, + "y": 1873.4599, + "direction": 4 + }, + "meta": { + "updateAt": 1754459890423, + "createAt": 1754459890423 + } + }, + "9QMmEY_TEQZNT5sDmA6Yz": { + "id": "9QMmEY_TEQZNT5sDmA6Yz", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "himTd51e5v3kRUyGYe4NN", + "columnIds": [ + "8j7EyYMI81x9vyXb19mYx" + ], + "x": 281.9433, + "y": 552.9794999999999, + "direction": 8 + }, + "end": { + "tableId": "zP_GWMpvpT6wH7qn9vxdv", + "columnIds": [ + "eVoU1yjNQJOI-PIkl4URb" + ], + "x": 288.6853, + "y": 673.8832, + "direction": 4 + }, + "meta": { + "updateAt": 1754460105807, + "createAt": 1754460105807 + } + }, + "uZYdKkxyiZvHumfOOANY1": { + "id": "uZYdKkxyiZvHumfOOANY1", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "DFKh-HfnYWxi11eZCqkgK", + "columnIds": [ + "Xqf75OtW1XCwmT8ZaOulD" + ], + "x": 1588.8926, + "y": 1354.192, + "direction": 2 + }, + "end": { + "tableId": "7_V9K4Pn6pwpgUiJ7FT-V", + "columnIds": [ + "MYB8w1R7Zwiykn_daUJka" + ], + "x": 1688.8813, + "y": 1439.6009, + "direction": 1 + }, + "meta": { + "updateAt": 1754460569184, + "createAt": 1754460569184 + } + }, + "gaL7EcdRZlWLEmqGGQXM2": { + "id": "gaL7EcdRZlWLEmqGGQXM2", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "DFKh-HfnYWxi11eZCqkgK", + "columnIds": [ + "Xqf75OtW1XCwmT8ZaOulD" + ], + "x": 1588.8926, + "y": 1278.192, + "direction": 2 + }, + "end": { + "tableId": "Yw3xkCepE7qeZkn3zo3TY", + "columnIds": [ + "-zfxKNUMEPmIfXHgfQUEI" + ], + "x": 1687.2255, + "y": 1280.1783, + "direction": 1 + }, + "meta": { + "updateAt": 1754460673601, + "createAt": 1754460673601 + } + }, + "yYEuTA5B9sEdoflkTb9JY": { + "id": "yYEuTA5B9sEdoflkTb9JY", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "xGf94aIdnNFbMawDHNsnn", + "columnIds": [ + "7VU2lYLaGsl1fq2uL-PCi" + ], + "x": 2585.5928, + "y": 183.5052, + "direction": 4 + }, + "end": { + "tableId": "dMgB23-XyfyBrQtln8T6A", + "columnIds": [ + "aCkS4Nc3SibCy7Y-AdeVg" + ], + "x": 2583.3898, + "y": 171.2988, + "direction": 8 + }, + "meta": { + "updateAt": 1754462286448, + "createAt": 1754462286448 + } + }, + "B1WkiiWsU0UzYoNPS3q8Z": { + "id": "B1WkiiWsU0UzYoNPS3q8Z", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "dMgB23-XyfyBrQtln8T6A", + "columnIds": [ + "4QYIJ98XjTW50SND0UHEI" + ], + "x": 2583.3898, + "y": 147.2988, + "direction": 8 + }, + "end": { + "tableId": "xGf94aIdnNFbMawDHNsnn", + "columnIds": [ + "byPDJPm_NdSCPxcJAcGXz" + ], + "x": 2591.7784, + "y": 237.1135, + "direction": 4 + }, + "meta": { + "updateAt": 1754462308698, + "createAt": 1754462308698 + } + }, + "kWi-bO9pQju-bdcAxODHV": { + "id": "kWi-bO9pQju-bdcAxODHV", + "identification": false, + "relationshipType": 2, + "startRelationshipType": 2, + "start": { + "tableId": "P4rpGmH6RmTcoRD_sWJW4", + "columnIds": [ + "JN_ZE5P9VpDahCHy4E08q" + ], + "x": 1871.1405, + "y": 784.5531, + "direction": 4 + }, + "end": { + "tableId": "fMdGmsjMSQ2cLtrNcE1gk", + "columnIds": [ + "3l2vZif9UhAa-4m-XaPk_" + ], + "x": 1867.8851, + "y": 753.1707, + "direction": 8 + }, + "meta": { + "updateAt": 1754527683467, + "createAt": 1754527683467 + } + }, + "co1cKJ8nBRLt8YAfd1tdb": { + "id": "co1cKJ8nBRLt8YAfd1tdb", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "fMdGmsjMSQ2cLtrNcE1gk", + "columnIds": [ + "EI3awnEHwoSx-MFH1eeET" + ], + "x": 1580.8728, + "y": 1690, + "direction": 2 + }, + "end": { + "tableId": "P4rpGmH6RmTcoRD_sWJW4", + "columnIds": [ + "n5lhDVesQxPdfub7uiz1E" + ], + "x": 1726.4452, + "y": 1690.2118, + "direction": 1 + }, + "meta": { + "updateAt": 1754527692448, + "createAt": 1754527692448 + } + }, + "MvtwedswfxkFsNDZKsJ6b": { + "id": "MvtwedswfxkFsNDZKsJ6b", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "fMdGmsjMSQ2cLtrNcE1gk", + "columnIds": [ + "EI3awnEHwoSx-MFH1eeET" + ], + "x": 1398.3728, + "y": 1730, + "direction": 8 + }, + "end": { + "tableId": "O6gR-_ZtKDlJpb0wAElPA", + "columnIds": [ + "9mv7C3PejUCqVC7TOR17M" + ], + "x": 1399.1894, + "y": 1891.8697, + "direction": 4 + }, + "meta": { + "updateAt": 1754527854297, + "createAt": 1754527854297 + } + } + }, + "indexEntities": {}, + "indexColumnEntities": {}, + "memoEntities": { + "kCNm2V-KbwITsahSuWOCX": { + "id": "kCNm2V-KbwITsahSuWOCX", + "value": "부서변동을 하면 사용자 새로운 OID를 채번(ID는 같을 수 있음)\n해당 OID 기반으로 부서변동에 따른 통계값 분리", + "ui": { + "x": 1054.9635, + "y": 106.2577, + "zIndex": 126, + "width": 135, + "height": 100, + "color": "" + }, + "meta": { + "updateAt": 1754442343323, + "createAt": 1754438782534 + } + }, + "q8IDmewsqx2pRUZ-diT_g": { + "id": "q8IDmewsqx2pRUZ-diT_g", + "value": "", + "ui": { + "x": 832.9738, + "y": 329.9999, + "zIndex": 202, + "width": 116, + "height": 100, + "color": "" + }, + "meta": { + "updateAt": 1754439126486, + "createAt": 1754439117514 + } + }, + "6-xqx_6-IL_WD9M7bjIcB": { + "id": "6-xqx_6-IL_WD9M7bjIcB", + "value": "열람 권한 - 사용자 마이폴더간 관계 설정이 필요할 수 있음", + "ui": { + "x": 839.9738, + "y": 329.9999, + "zIndex": 203, + "width": 116, + "height": 100, + "color": "" + }, + "meta": { + "updateAt": 1754439260012, + "createAt": 1754439202510 + } + }, + "0ClO5nwlRZT5T7H7vf8FC": { + "id": "0ClO5nwlRZT5T7H7vf8FC", + "value": "열람권한에 따른 ", + "ui": { + "x": 892.9738, + "y": 384.9999, + "zIndex": 205, + "width": 116, + "height": 100, + "color": "" + }, + "meta": { + "updateAt": 1754439338173, + "createAt": 1754439328458 + } + }, + "tqgaaX4eEzP1nibRXVfGg": { + "id": "tqgaaX4eEzP1nibRXVfGg", + "value": "\"마이폴더\"에 저장된 유전체 열람권한 고려해야함.\n참조된 유전체 oid 기준 벌크 Update로 만료일 수정 고려", + "ui": { + "x": 799.1914, + "y": 336.7698, + "zIndex": 477, + "width": 117, + "height": 100, + "color": "" + }, + "meta": { + "updateAt": 1754460137417, + "createAt": 1754445861879 + } + }, + "pF_-UJJxAyfhWI3x2tlPZ": { + "id": "pF_-UJJxAyfhWI3x2tlPZ", + "value": "RFP 15~21번에 대한 내용을 포함 할수 있는 관계인지 검토 필요", + "ui": { + "x": 980.5211, + "y": 1321.3055, + "zIndex": 702, + "width": 116, + "height": 100, + "color": "" + }, + "meta": { + "updateAt": 1754460639043, + "createAt": 1754459291371 + } + }, + "rKhGovSsu7i1je5nndRU9": { + "id": "rKhGovSsu7i1je5nndRU9", + "value": "유전자 하위 관계 설정은 추후에 정의\n(더모아젠에서 구성해주는 구조에 따라 변동 가능성 있음)", + "ui": { + "x": 2264.717, + "y": 367.0731, + "zIndex": 904, + "width": 116, + "height": 513, + "color": "#E91E63" + }, + "meta": { + "updateAt": 1754523214812, + "createAt": 1754464842551 + } + }, + "20v-u6W1RTXPdURllp2az": { + "id": "20v-u6W1RTXPdURllp2az", + "value": "배양정보는 별도", + "ui": { + "x": 2061.8113, + "y": 805.2846, + "zIndex": 976, + "width": 116, + "height": 100, + "color": "" + }, + "meta": { + "updateAt": 1754527647736, + "createAt": 1754527610253 + } + } + } + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 2f5e3a6..314d911 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,9 @@ +buildscript { + ext { + queryDslVersion = "5.0.0" + } +} + plugins { id 'java' id 'org.springframework.boot' version '3.5.4' @@ -23,12 +29,6 @@ repositories { mavenCentral() } -buildscript { - ext { - queryDslVersion = "5.0.0" - } -} - dependencies { // 개발용 의존성 추가 developmentOnly 'org.springframework.boot:spring-boot-devtools' @@ -36,6 +36,19 @@ dependencies { // PostgreSQL JDBC 드라이버 runtimeOnly 'org.postgresql:postgresql' implementation 'org.springframework.boot:spring-boot-starter-web' + + // Spring Security 추가 + implementation 'org.springframework.boot:spring-boot-starter-security' + + // Validation 추가 + implementation 'org.springframework.boot:spring-boot-starter-validation' + + // ModelMapper 추가 + implementation 'org.modelmapper:modelmapper:3.1.1' + + // MyBatis 추가 + implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3' + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/ddl/schema.sql b/ddl/schema.sql new file mode 100644 index 0000000..1ca85ee --- /dev/null +++ b/ddl/schema.sql @@ -0,0 +1,14 @@ + + create table member ( + status varchar(1) not null, + created_at timestamp(6) not null, + last_login_at timestamp(6), + oid bigint generated by default as identity, + updated_at timestamp(6) not null, + role varchar(40) not null, + password varchar(100) not null, + user_id varchar(100) not null, + refresh_token varchar(200), + primary key (oid), + constraint uk_member_user_id unique (user_id) + ); diff --git a/src/main/java/com/bio/bio_backend/BioBackendApplication.java b/src/main/java/com/bio/bio_backend/BioBackendApplication.java index f5364af..58ec790 100644 --- a/src/main/java/com/bio/bio_backend/BioBackendApplication.java +++ b/src/main/java/com/bio/bio_backend/BioBackendApplication.java @@ -2,8 +2,10 @@ package com.bio.bio_backend; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing public class BioBackendApplication { public static void main(String[] args) { diff --git a/src/main/java/com/bio/bio_backend/domain/controller/TestController.java b/src/main/java/com/bio/bio_backend/domain/controller/TestController.java deleted file mode 100644 index 2160ad2..0000000 --- a/src/main/java/com/bio/bio_backend/domain/controller/TestController.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.bio.bio_backend.controller; - -import java.util.List; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.bio.bio_backend.entity.Test; -import com.bio.bio_backend.repository.TestRepository; - -@RestController -@RequestMapping("/api/test") -public class TestController { - - private final TestRepository repository; - - public TestController(TestRepository repository){ - this.repository = repository; - } - - @GetMapping - public List getAllUsers(){ - System.out.println("test10099!!"); - return repository.findAll(); - } - - @PostMapping - public Test creatTest(@RequestBody Test test){ - return repository.save(test); - } -} diff --git a/src/main/java/com/bio/bio_backend/domain/entity/Test.java b/src/main/java/com/bio/bio_backend/domain/entity/Test.java deleted file mode 100644 index 89323c1..0000000 --- a/src/main/java/com/bio/bio_backend/domain/entity/Test.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.bio.bio_backend.entity; - -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; - -@Entity -public class Test { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - - private Long id; - - private String name; - private String email; - -} diff --git a/src/main/java/com/bio/bio_backend/domain/repository/TestRepository.java b/src/main/java/com/bio/bio_backend/domain/repository/TestRepository.java deleted file mode 100644 index 1bc5b39..0000000 --- a/src/main/java/com/bio/bio_backend/domain/repository/TestRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.bio.bio_backend.repository; - -import org.springframework.data.jpa.repository.JpaRepository; - -import com.bio.bio_backend.entity.Test; - -public interface TestRepository extends JpaRepository{ - -} diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/controller/MemberController.java b/src/main/java/com/bio/bio_backend/domain/user/member/controller/MemberController.java new file mode 100644 index 0000000..2c1c066 --- /dev/null +++ b/src/main/java/com/bio/bio_backend/domain/user/member/controller/MemberController.java @@ -0,0 +1,125 @@ +package com.bio.bio_backend.domain.user.member.controller; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.modelmapper.ModelMapper; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.validation.Valid; +import com.bio.bio_backend.domain.user.member.dto.MemberDTO; +import com.bio.bio_backend.domain.user.member.dto.CreateMemberRequestDTO; +import com.bio.bio_backend.domain.user.member.dto.CreateMemberResponseDto; +import com.bio.bio_backend.domain.user.member.service.MemberService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@RestController +@RequiredArgsConstructor +@Slf4j +public class MemberController { + + private final MemberService memberService; + private final ModelMapper mapper; + private final BCryptPasswordEncoder bCryptPasswordEncoder; + + + + @PostMapping("/join") + public ResponseEntity createMember(@RequestBody @Valid CreateMemberRequestDTO requestDto) { + + // RequestMember를 MemberDTO로 변환 + MemberDTO member = new MemberDTO(); + member.setId(requestDto.getUserId()); + member.setPw(requestDto.getPassword()); + + int oid = memberService.createMember(member); + + // 생성된 회원 정보를 조회하여 응답 + MemberDTO createdMember = memberService.selectMember(oid); + CreateMemberResponseDto responseDto = mapper.map(createdMember, CreateMemberResponseDto.class); + + return ResponseEntity.status(HttpStatus.CREATED).body(responseDto); + } + + // @PostMapping("/member/list") + // public ResponseEntity> getMemberList(@RequestBody(required = false) Map params) { + + // if(params == null){ + // params = new HashMap<>(); + // } + + // Iterable memberList = memberService.selectMemberList(params); + + // List result = new ArrayList<>(); + + // memberList.forEach(m -> { + // result.add(new ModelMapper().map(m, ResponseMember.class)); + // }); + + // return ResponseEntity.status(HttpStatus.OK).body(result); + // } + + // @GetMapping("/member/{seq}") + // public ResponseEntity selectMember(@PathVariable("seq") int seq) { + + // MemberDTO member = memberService.selectMember(seq); + + // ResponseMember responseMember = mapper.map(member, ResponseMember.class); + + // return ResponseEntity.status(HttpStatus.OK).body(responseMember); + // } + + // @PutMapping("/member") + // public CustomApiResponse updateMember(@RequestBody @Valid CreateMemberRequestDTO requestMember, @AuthenticationPrincipal MemberDTO registrant) { + // // 현재 JWT는 사용자 id 값을 통하여 생성, 회원정보 변경 시 JWT 재발급 여부 검토 + + // MemberDTO member = mapper.map(requestMember, MemberDTO.class); + + // if (requestMember.getPassword() != null) { + // member.setPw(bCryptPasswordEncoder.encode(requestMember.getPassword())); + // } + + // member.setRegSeq(registrant.getSeq()); + // memberService.updateMember(member); + + // return CustomApiResponse.success(ApiResponseCode.USER_INFO_CHANGE, null); + // } + + // @DeleteMapping("/member") + // public CustomApiResponse deleteMember(@RequestBody @Valid CreateMemberRequestDTO requestMember){ + + // MemberDTO member = mapper.map(requestMember, MemberDTO.class); + + // memberService.deleteMember(member); + + // return CustomApiResponse.success(ApiResponseCode.USER_DELETE_SUCCESSFUL, null); + // } + + // @PostMapping("/logout") + // public CustomApiResponse logout(@AuthenticationPrincipal MemberDTO member) { + + // String id = member.getId(); + + // try { + // memberService.deleteRefreshToken(id); + // } catch (Exception e) { + // return CustomApiResponse.fail(ApiResponseCode.INTERNAL_SERVER_ERROR, null); + // } + + // return CustomApiResponse.success(ApiResponseCode.LOGOUT_SUCCESSFUL, null); + // } + +} diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberRequestDTO.java b/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberRequestDTO.java new file mode 100644 index 0000000..016e655 --- /dev/null +++ b/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberRequestDTO.java @@ -0,0 +1,17 @@ +package com.bio.bio_backend.domain.user.member.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CreateMemberRequestDTO { + + @NotBlank(message = "사용자 ID는 필수입니다") + private String userId; + + @NotBlank(message = "비밀번호는 필수입니다") + private String password; + +} diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberResponseDto.java b/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberResponseDto.java new file mode 100644 index 0000000..187594b --- /dev/null +++ b/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberResponseDto.java @@ -0,0 +1,11 @@ +package com.bio.bio_backend.domain.user.member.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CreateMemberResponseDto { + private String id; + private String pw; +} diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/dto/MemberDTO.java b/src/main/java/com/bio/bio_backend/domain/user/member/dto/MemberDTO.java new file mode 100644 index 0000000..7244a24 --- /dev/null +++ b/src/main/java/com/bio/bio_backend/domain/user/member/dto/MemberDTO.java @@ -0,0 +1,127 @@ +package com.bio.bio_backend.domain.user.member.dto; + +import java.sql.Timestamp; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import com.bio.bio_backend.global.constants.MemberConstants; +import lombok.Data; + +@Data +/** + * 회원 + */ +public class MemberDTO implements UserDetails { + /** + * 시퀀스 (PK) + */ + private int seq; + + /** + * ID + */ + private String id; + + /** + * Password + */ + private String pw; + + /** + * 권한 + */ + private String role; + + /** + * 회원 상태 + */ + private String status; + + /** + * 가입 일시 + */ + private Timestamp regAt; + + /** + * 등록자 + */ + private int regSeq; + + /** + * 수정 일시 + */ + private Timestamp udtAt; + + /** + * 수정자 + */ + private int udtSeq; + + /** + * 최근 로그인 일시 + */ + private Timestamp lastLoginAt; + + /** + * Refresh Token + */ + private String refreshToken; + + @Override + public Collection getAuthorities() { + + Set roles = new HashSet<>(); + String auth = ""; + + + if(role.equals("SYSTEM_ADMIN")){ + auth = MemberConstants.ROLE_SYSTEM_ADMIN + "," + + MemberConstants.ROLE_ADMIN + "," + MemberConstants.ROLE_MEMBER; + }else if(role.equals("ADMIN")){ + auth = MemberConstants.ROLE_ADMIN + "," + MemberConstants.ROLE_MEMBER; + }else { + auth = MemberConstants.ROLE_MEMBER; + } + + for (String x : auth.split(",")) { + roles.add(new SimpleGrantedAuthority(x)); + } + + return roles; + } + + @Override + public String getPassword() { + return pw; + } + + @Override + public String getUsername() { + return id; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/entity/Member.java b/src/main/java/com/bio/bio_backend/domain/user/member/entity/Member.java new file mode 100644 index 0000000..77ba685 --- /dev/null +++ b/src/main/java/com/bio/bio_backend/domain/user/member/entity/Member.java @@ -0,0 +1,46 @@ +package com.bio.bio_backend.domain.user.member.entity; + +import java.time.LocalDateTime; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import com.bio.bio_backend.global.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Getter @Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Table( + name = "member", + uniqueConstraints = { + @UniqueConstraint(name = "uk_member_user_id", columnNames = "user_id") + } +) +public class Member extends BaseEntity { + + @Column(name = "user_id", nullable = false, length = 100) + private String userId; + + @Column(name = "password", nullable = false, length = 100) + private String password; + + @Column(name = "role", nullable = false, length = 40) + private String role; + + @Column(name = "status", nullable = false, length = 1) + private String status; + + @Column(name = "refresh_token", length = 200) + private String refreshToken; + + @Column(name = "last_login_at") + private LocalDateTime lastLoginAt; +} \ No newline at end of file diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/mapper/MemberMapper.java b/src/main/java/com/bio/bio_backend/domain/user/member/mapper/MemberMapper.java new file mode 100644 index 0000000..cfc6c08 --- /dev/null +++ b/src/main/java/com/bio/bio_backend/domain/user/member/mapper/MemberMapper.java @@ -0,0 +1,27 @@ +package com.bio.bio_backend.domain.user.member.mapper; + +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.annotations.Mapper; + +import com.bio.bio_backend.domain.user.member.dto.MemberDTO; + +@Mapper +public interface MemberMapper { + int createMember(MemberDTO memberDTO); + + MemberDTO loadUserByUsername(String id); + + void updateRefreshToken(MemberDTO memberDTO); + + String getRefreshToken(String id); + + int deleteRefreshToken(String id); + + List selectMemberList(Map params); + + MemberDTO selectMemberBySeq(int seq); + + int updateMember(MemberDTO member); +} diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepository.java b/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepository.java new file mode 100644 index 0000000..8ff56f2 --- /dev/null +++ b/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepository.java @@ -0,0 +1,16 @@ +package com.bio.bio_backend.domain.user.member.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.bio.bio_backend.domain.user.member.entity.Member; + +@Repository +public interface MemberRepository extends JpaRepository { + + // 사용자 ID로 회원 조회 + Member findByUserId(String userId); + + // 사용자 ID 존재 여부 확인 + boolean existsByUserId(String userId); +} \ No newline at end of file diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryCustom.java b/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryCustom.java new file mode 100644 index 0000000..c553ea7 --- /dev/null +++ b/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryCustom.java @@ -0,0 +1,68 @@ +package com.bio.bio_backend.domain.user.member.repository; + +import com.bio.bio_backend.domain.user.member.entity.Member; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.List; +import java.util.Optional; + +/** + * QueryDSL을 활용한 커스텀 쿼리 메서드들을 정의하는 인터페이스 + * 복잡한 쿼리나 동적 쿼리가 필요한 경우 이 인터페이스를 구현하여 사용합니다. + */ +public interface MemberRepositoryCustom { + + /** + * 사용자 ID로 회원을 조회합니다. + * QueryDSL을 사용하여 더 유연한 쿼리 작성이 가능합니다. + * + * @param userId 사용자 ID + * @return Optional 회원 정보 (없으면 empty) + */ + Optional findByUserIdCustom(String userId); + + /** + * 역할(Role)별로 회원 목록을 조회합니다. + * + * @param role 회원 역할 + * @return List 해당 역할을 가진 회원 목록 + */ + List findByRole(String role); + + /** + * 상태(Status)별로 회원 목록을 조회합니다. + * + * @param status 회원 상태 + * @return List 해당 상태를 가진 회원 목록 + */ + List findByStatus(String status); + + /** + * 사용자 ID와 상태로 회원을 조회합니다. + * + * @param userId 사용자 ID + * @param status 회원 상태 + * @return Optional 회원 정보 + */ + Optional findByUserIdAndStatus(String userId, String status); + + /** + * 검색 조건에 따른 회원 목록을 페이징하여 조회합니다. + * + * @param userId 사용자 ID (부분 검색) + * @param role 회원 역할 + * @param status 회원 상태 + * @param pageable 페이징 정보 + * @return Page 페이징된 회원 목록 + */ + Page findMembersByCondition(String userId, String role, String status, Pageable pageable); + + /** + * 마지막 로그인 시간이 특정 시간 이후인 회원들을 조회합니다. + * + * @param lastLoginAfter 마지막 로그인 기준 시간 + * @return List 해당 조건을 만족하는 회원 목록 + */ + List findActiveMembersByLastLogin(java.time.LocalDateTime lastLoginAfter); +} diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryImpl.java b/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryImpl.java new file mode 100644 index 0000000..914c419 --- /dev/null +++ b/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryImpl.java @@ -0,0 +1,130 @@ +package com.bio.bio_backend.domain.user.member.repository; + +import com.bio.bio_backend.domain.user.member.entity.Member; +import com.bio.bio_backend.domain.user.member.entity.QMember; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +/** + * QueryDSL을 활용하여 MemberRepositoryCustom 인터페이스를 구현하는 클래스 + * 복잡한 쿼리나 동적 쿼리를 QueryDSL로 작성하여 성능과 가독성을 향상시킵니다. + */ +@Repository +@RequiredArgsConstructor +public class MemberRepositoryImpl implements MemberRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + /** + * QMember 인스턴스를 생성하여 쿼리에서 사용합니다. + * QueryDSL의 Q클래스를 통해 타입 안전한 쿼리 작성이 가능합니다. + */ + private final QMember member = QMember.member; + + @Override + public Optional findByUserIdCustom(String userId) { + // QueryDSL을 사용하여 사용자 ID로 회원을 조회합니다. + // eq() 메서드를 사용하여 정확한 일치 조건을 설정합니다. + Member foundMember = queryFactory + .selectFrom(member) + .where(member.userId.eq(userId)) + .fetchOne(); + + return Optional.ofNullable(foundMember); + } + + @Override + public List findByRole(String role) { + // 역할별로 회원을 조회합니다. + // eq() 메서드를 사용하여 정확한 일치 조건을 설정합니다. + return queryFactory + .selectFrom(member) + .where(member.role.eq(role)) + .fetch(); + } + + @Override + public List findByStatus(String status) { + // 상태별로 회원을 조회합니다. + return queryFactory + .selectFrom(member) + .where(member.status.eq(status)) + .fetch(); + } + + @Override + public Optional findByUserIdAndStatus(String userId, String status) { + // 사용자 ID와 상태를 모두 만족하는 회원을 조회합니다. + // and() 메서드를 사용하여 여러 조건을 결합합니다. + Member foundMember = queryFactory + .selectFrom(member) + .where(member.userId.eq(userId) + .and(member.status.eq(status))) + .fetchOne(); + + return Optional.ofNullable(foundMember); + } + + @Override + public Page findMembersByCondition(String userId, String role, String status, Pageable pageable) { + // BooleanBuilder를 사용하여 동적 쿼리를 구성합니다. + // null이 아닌 조건만 쿼리에 포함시킵니다. + BooleanBuilder builder = new BooleanBuilder(); + + // 사용자 ID가 제공된 경우 부분 검색 조건을 추가합니다. + if (userId != null && !userId.trim().isEmpty()) { + builder.and(member.userId.containsIgnoreCase(userId)); + } + + // 역할이 제공된 경우 정확한 일치 조건을 추가합니다. + if (role != null && !role.trim().isEmpty()) { + builder.and(member.role.eq(role)); + } + + // 상태가 제공된 경우 정확한 일치 조건을 추가합니다. + if (status != null && !status.trim().isEmpty()) { + builder.and(member.status.eq(status)); + } + + // 전체 개수를 조회합니다. + long total = queryFactory + .selectFrom(member) + .where(builder) + .fetchCount(); + + // 페이징 조건을 적용하여 결과를 조회합니다. + List content = queryFactory + .selectFrom(member) + .where(builder) + .orderBy(member.createdAt.desc()) // 생성일 기준 내림차순 정렬 + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + // Page 객체를 생성하여 반환합니다. + return new PageImpl<>(content, pageable, total); + } + + @Override + public List findActiveMembersByLastLogin(LocalDateTime lastLoginAfter) { + // 마지막 로그인 시간이 특정 시간 이후인 활성 회원들을 조회합니다. + // 여러 조건을 조합하여 복잡한 쿼리를 작성합니다. + return queryFactory + .selectFrom(member) + .where(member.status.eq("A") // 활성 상태 + .and(member.lastLoginAt.isNotNull()) // 마지막 로그인 시간이 존재 + .and(member.lastLoginAt.after(lastLoginAfter))) // 특정 시간 이후 + .orderBy(member.lastLoginAt.desc()) // 마지막 로그인 시간 기준 내림차순 정렬 + .fetch(); + } +} diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberService.java b/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberService.java new file mode 100644 index 0000000..5ee825e --- /dev/null +++ b/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberService.java @@ -0,0 +1,30 @@ +package com.bio.bio_backend.domain.user.member.service; + +import java.util.List; +import java.util.Map; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; + +import com.bio.bio_backend.domain.user.member.dto.MemberDTO; + +public interface MemberService extends UserDetailsService { + + UserDetails loadUserByUsername(String id); + + int createMember(MemberDTO memberDTO); + + void updateRefreshToken(MemberDTO memberDTO); + + String getRefreshToken(String id); + + int deleteRefreshToken(String id); + + List selectMemberList(Map params); + + MemberDTO selectMember(int seq); + + int updateMember(MemberDTO member); + + int deleteMember(MemberDTO member); +} diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberServiceImpl.java b/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberServiceImpl.java new file mode 100644 index 0000000..f061dac --- /dev/null +++ b/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberServiceImpl.java @@ -0,0 +1,95 @@ +package com.bio.bio_backend.domain.user.member.service; + +import com.bio.bio_backend.domain.user.member.dto.MemberDTO; +import com.bio.bio_backend.domain.user.member.entity.Member; +import com.bio.bio_backend.domain.user.member.mapper.MemberMapper; +import com.bio.bio_backend.domain.user.member.repository.MemberRepository; +import com.bio.bio_backend.global.constants.MemberConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +@Service +@RequiredArgsConstructor +@Slf4j +public class MemberServiceImpl implements MemberService { + + private final MemberMapper memberMapper; + private final MemberRepository memberRepository; + private final BCryptPasswordEncoder bCryptPasswordEncoder; + + @Override + public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException { + + MemberDTO member = memberMapper.loadUserByUsername(id); + + if (member == null) { + throw new UsernameNotFoundException("User not found with id : " + id); + } + + return member; + } + + @Override + public int createMember(MemberDTO memberDTO) { + // JPA Entity를 사용하여 회원 생성 + Member member = Member.builder() + .userId(memberDTO.getId()) + .password(bCryptPasswordEncoder.encode(memberDTO.getPw())) + .role(MemberConstants.ROLE_MEMBER) + .status(MemberConstants.MEMBER_ACTIVE) + .build(); + + // JPA 레파지토리를 통해 저장 + Member savedMember = memberRepository.save(member); + + // 저장된 회원의 oid를 반환 + return savedMember.getOid().intValue(); + } + + @Override + public void updateRefreshToken(MemberDTO memberDTO) { + memberMapper.updateRefreshToken(memberDTO); + } + + @Override + public String getRefreshToken(String id) { + return memberMapper.getRefreshToken(id); + } + + @Override + public int deleteRefreshToken(String id) { + return memberMapper.deleteRefreshToken(id); + } + + @Override + public List selectMemberList(Map params) { + return memberMapper.selectMemberList(params); + } + + @Override + public MemberDTO selectMember(int seq) { + return memberMapper.selectMemberBySeq(seq); + } + + @Override + public int updateMember(MemberDTO member) { + return memberMapper.updateMember(member); + } + + @Override + public int deleteMember(MemberDTO member) { + + member.setStatus(MemberConstants.MEMBER_INACTIVE); + + log.info(member.toString()); + + return memberMapper.updateMember(member); + } +} diff --git a/src/main/java/com/bio/bio_backend/global/aop/RepositoryLoggingAspect.java b/src/main/java/com/bio/bio_backend/global/aop/RepositoryLoggingAspect.java index 6bc5d3b..71d0600 100644 --- a/src/main/java/com/bio/bio_backend/global/aop/RepositoryLoggingAspect.java +++ b/src/main/java/com/bio/bio_backend/global/aop/RepositoryLoggingAspect.java @@ -1,4 +1,4 @@ -package com.qsl.qsl_tutorial.base; +package com.bio.bio_backend.global.aop; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; @@ -6,24 +6,51 @@ import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; -@Aspect -@Component -@Slf4j +/** + * Repository 계층의 메서드 호출을 로깅하는 AOP(Aspect-Oriented Programming) 클래스 + * 모든 Repository 인터페이스의 메서드 호출 시점과 실행 시간을 로그로 기록합니다. + */ +@Aspect // AOP 기능을 활성화하는 어노테이션 +@Component // Spring Bean으로 등록하는 어노테이션 +@Slf4j // Lombok의 로깅 기능을 제공하는 어노테이션 public class RepositoryLoggingAspect { + + /** + * Repository 계층의 모든 메서드 호출을 가로채서 로깅하는 Around 어드바이스 + * + * @param pjp ProceedingJoinPoint - 실행될 메서드의 정보를 담고 있는 객체 + * @return Object - 원본 메서드의 실행 결과 + * @throws Throwable - 원본 메서드에서 발생할 수 있는 예외 + */ @Around("execution(* org.springframework.data.repository.Repository+.*(..))") public Object logQueryCall(ProceedingJoinPoint pjp) throws Throwable { + // 메서드 실행 시작 시간을 기록 long t0 = System.currentTimeMillis(); + + // 실행될 메서드의 클래스명과 메서드명을 추출 String type = pjp.getSignature().getDeclaringTypeName(); String method = pjp.getSignature().getName(); + + // 메서드 호출 시 전달되는 매개변수들을 추출 Object[] args = pjp.getArgs(); + // 메서드 호출 시작을 로그로 기록 log.info("[QUERY CALL] {}.{}(args={})", type, method, java.util.Arrays.toString(args)); + try { + // 원본 메서드를 실행 Object result = pjp.proceed(); + + // 메서드 실행 완료를 로그로 기록 (실행 시간 포함) log.info("[QUERY DONE] {}.{}() in {}ms", type, method, (System.currentTimeMillis() - t0)); + + // 원본 메서드의 결과를 반환 return result; } catch (Throwable ex) { + // 메서드 실행 중 예외 발생 시 로그로 기록 log.warn("[QUERY FAIL] {}.{}() -> {}", type, method, ex.toString()); + + // 예외를 다시 던져서 원래의 예외 처리 흐름을 유지 throw ex; } } diff --git a/src/main/java/com/bio/bio_backend/global/config/AppConfig.java b/src/main/java/com/bio/bio_backend/global/config/AppConfig.java index 49ded59..0b22fb7 100644 --- a/src/main/java/com/bio/bio_backend/global/config/AppConfig.java +++ b/src/main/java/com/bio/bio_backend/global/config/AppConfig.java @@ -1,14 +1,14 @@ -package com.qsl.qsl_tutorial.base; +package com.bio.bio_backend.global.config; -import com.querydsl.jpa.impl.JPAQueryFactory; -import jakarta.persistence.EntityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @Configuration public class AppConfig { - @Bean - public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) { - return new JPAQueryFactory(entityManager); - } + + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } } \ No newline at end of file diff --git a/src/main/java/com/bio/bio_backend/global/config/QuerydslConfig.java b/src/main/java/com/bio/bio_backend/global/config/QuerydslConfig.java new file mode 100644 index 0000000..c8a231a --- /dev/null +++ b/src/main/java/com/bio/bio_backend/global/config/QuerydslConfig.java @@ -0,0 +1,33 @@ +package com.bio.bio_backend.global.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * QueryDSL 설정을 위한 Configuration 클래스 + * JPAQueryFactory Bean을 등록하여 QueryDSL을 사용할 수 있도록 합니다. + */ +@Configuration +public class QuerydslConfig { + + /** + * JPA EntityManager를 주입받습니다. + * @PersistenceContext 어노테이션을 사용하여 Spring이 관리하는 EntityManager를 주입받습니다. + */ + @PersistenceContext + private EntityManager entityManager; + + /** + * JPAQueryFactory Bean을 생성하여 등록합니다. + * 이 Bean은 QueryDSL을 사용하는 Repository에서 주입받아 사용됩니다. + * + * @return JPAQueryFactory 인스턴스 + */ + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} diff --git a/src/main/java/com/bio/bio_backend/global/constants/MemberConstants.java b/src/main/java/com/bio/bio_backend/global/constants/MemberConstants.java new file mode 100644 index 0000000..99e4d98 --- /dev/null +++ b/src/main/java/com/bio/bio_backend/global/constants/MemberConstants.java @@ -0,0 +1,38 @@ +package com.bio.bio_backend.global.constants; + +/** + * Member 엔티티에서 사용하는 상수들을 정의하는 클래스 + * 역할(Role)과 상태(Status) 등의 상수값을 관리합니다. + */ +public class MemberConstants { + + /** + * 회원 역할 상수 + */ + public static final String ROLE_MEMBER = "MEMBER"; // 일반 회원 + public static final String ROLE_ADMIN = "ADMIN"; // 관리자 + public static final String ROLE_USER = "USER"; // 사용자 + public static final String ROLE_SYSTEM_ADMIN = "SYSTEM_ADMIN"; // 시스템 관리자 + + /** + * 회원 상태 상수 + */ + public static final String MEMBER_ACTIVE = "A"; // 활성 상태 (Active) + public static final String MEMBER_INACTIVE = "I"; // 비활성 상태 (Inactive) + public static final String MEMBER_SUSPENDED = "S"; // 정지 상태 (Suspended) + public static final String MEMBER_DELETED = "D"; // 삭제 상태 (Deleted) + + /** + * 기본값 상수 + */ + public static final String DEFAULT_ROLE = ROLE_MEMBER; // 기본 역할 + public static final String DEFAULT_STATUS = MEMBER_ACTIVE; // 기본 상태 + + /** + * 유효성 검사 상수 + */ + public static final int MIN_USER_ID_LENGTH = 4; // 사용자 ID 최소 길이 + public static final int MAX_USER_ID_LENGTH = 20; // 사용자 ID 최대 길이 + public static final int MIN_PASSWORD_LENGTH = 8; // 비밀번호 최소 길이 + public static final int MAX_PASSWORD_LENGTH = 100; // 비밀번호 최대 길이 +} diff --git a/src/main/java/com/bio/bio_backend/global/entity/BaseEntity.java b/src/main/java/com/bio/bio_backend/global/entity/BaseEntity.java new file mode 100644 index 0000000..1e56303 --- /dev/null +++ b/src/main/java/com/bio/bio_backend/global/entity/BaseEntity.java @@ -0,0 +1,66 @@ +package com.bio.bio_backend.global.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +/** + * 모든 엔티티가 상속받는 기본 엔티티 클래스 + * 공통 필드들을 정의하고 JPA Auditing을 지원합니다. + */ +@Getter +@Setter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseEntity { + + /** + * 엔티티의 고유 식별자 (Primary Key) + * 자동 증가하는 Long 타입으로 설정 + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "oid", nullable = false) + private Long oid; + + /** + * 엔티티 생성 시간 + * JPA Auditing을 통해 자동으로 설정됨 + */ + @CreatedDate + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + /** + * 엔티티 수정 시간 + * JPA Auditing을 통해 자동으로 설정됨 + */ + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + private LocalDateTime updatedAt; + + /** + * 엔티티 저장 전에 실행되는 메서드 + * 생성 시간과 수정 시간을 자동으로 설정 + */ + @PrePersist + protected void onCreate() { + LocalDateTime now = LocalDateTime.now(); + this.createdAt = now; + this.updatedAt = now; + } + + /** + * 엔티티 수정 전에 실행되는 메서드 + * 수정 시간을 자동으로 설정 + */ + @PreUpdate + protected void onUpdate() { + this.updatedAt = LocalDateTime.now(); + } +}