Skip to content

Commit fa6a244

Browse files
authored
Merge pull request #60 from oslabs-beta/ycbran
Ycbran
2 parents 5454af4 + f3447dd commit fa6a244

File tree

10 files changed

+146
-40
lines changed

10 files changed

+146
-40
lines changed

.cert/ca.crt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDXjCCAkagAwIBAgIFODk4NDAwDQYJKoZIhvcNAQELBQAwXjEQMA4GA1UEAxMH
3+
VGVzdCBDQTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNV
4+
BAcTDVNhbiBGcmFuY2lzY28xEDAOBgNVBAoTB1Rlc3QgQ0EwHhcNMjMwNTE2MTcw
5+
NDU3WhcNMjQwNTE1MTcwNDU3WjBeMRAwDgYDVQQDEwdUZXN0IENBMQswCQYDVQQG
6+
EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
7+
bzEQMA4GA1UEChMHVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
8+
ggEBAKW0Pn146jzobSeRPRYa89bC5eLGerl1gJC8QaMegCjzNKbc9c6D3LLWd6rc
9+
I81Drb64wV7fBzpJy1VWq4tAFw1KUKS8J5UKug8HbpPSTdfSq0HyFWKlKXjHOhCw
10+
FknwdiY2ttlQBQXPhVXUEX50WQp/4q4YY3oJmkLEE2GhcVGG9M8usR6I8uQ0k5WD
11+
oO4Zl4SIP2KAUChEsBOoConAwYK5Z+4H6COujCQZ1KnbhkMbd6aNHpyTntHtVseF
12+
HxLYKcbKepllme2AAo1TPln758BWoruLueL7Ck3qczBWYICYil1g3WnBjn+oyIAp
13+
ARwzQ54hsQfJ+JrF3Qfa8tpQNV0CAwEAAaMjMCEwDwYDVR0TAQH/BAUwAwEB/zAO
14+
BgNVHQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADggEBAC3XvlQfqmb64r2m+igx
15+
LJS4QuLUM/RiNrAj+7kM5h9LxsJDNUi2X4/jBqIGaoCUtugE3UxDhhYtjnvC2OWv
16+
+gXJncQ8+5gYHpYhj8g4JYe+PCOvhrLEMRHM04/ArYLUqeM2eGdkg+4kQtmiDwUO
17+
qXm9ppt5DfEHYii14Iq+dQ9w7lPaNO+lOf+L4guD6Bfbkni4m7IIl3SwLcylJob+
18+
+MQNI6i6eyZavPvR/JP5NdBj0cTTHB4KZN8DYQA+Lztf+n3Cj7MkAgE2V0ew9MNi
19+
mRBtSzgn0fwZkiNyVZ0jxIl1ZiEmnhihTr09znGgliDQcSpMqRpVBB0FFFYH25nZ
20+
6Mc=
21+
-----END CERTIFICATE-----

.cert/ca.key

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEowIBAAKCAQEApbQ+fXjqPOhtJ5E9Fhrz1sLl4sZ6uXWAkLxBox6AKPM0ptz1
3+
zoPcstZ3qtwjzUOtvrjBXt8HOknLVVari0AXDUpQpLwnlQq6Dwduk9JN19KrQfIV
4+
YqUpeMc6ELAWSfB2Jja22VAFBc+FVdQRfnRZCn/irhhjegmaQsQTYaFxUYb0zy6x
5+
Hojy5DSTlYOg7hmXhIg/YoBQKESwE6gKicDBgrln7gfoI66MJBnUqduGQxt3po0e
6+
nJOe0e1Wx4UfEtgpxsp6mWWZ7YACjVM+WfvnwFaiu4u54vsKTepzMFZggJiKXWDd
7+
acGOf6jIgCkBHDNDniGxB8n4msXdB9ry2lA1XQIDAQABAoIBAA/cqKy3IekRsL08
8+
ZHYUv2m7/2iNalsNefCg7MGSHl+NJGmiti7+JWbDK/Jhn1W7WErtAam0vMriHII/
9+
SS2LxYkip4SVdE6Kl9PveLDSzVvPEVC/PTfTqpwiqhbF3GRYjd7LlwZHaCutuUxF
10+
gCU0Ov/LzSWwNPmOKMOiZF6QpznQA3m0F/0qMxvoFJrYdq2177C0d8+UeQNLGL0x
11+
xw83b+C1ClsWdMvIr0h2pqykeE/epr3XcLSBXIjpCkDFLoYeyIs1g9xDY+uVEdJn
12+
SsqR2L4k+mk+sL7jltqBWGqlVKFzRI+09gLiH9TVF6wUGww+dQvM0itRj1DwU46K
13+
jzFt1gECgYEAzi7JtsniU8PiepYM1EQl0AcXXuig57gcvvwsMfXUWexny8a1gwk5
14+
Dgw/hlPlvv21vfSIC3MCsw7fx0GiFJeSWNzHHjTLbdL7NGzf887+iuT9iBwy/k/6
15+
yYTYOeGPgN5JcVpnd8RYgVA+dLZqMWAaKmNRfmLGtvLQmH/vHpgH4l0CgYEAzb2w
16+
1FRGoiRANBizO6aPhYU6gF/oe890jgWcHRegdbNLd0zPc486TMW7c1sTTcasj5/F
17+
mSxhCGWkyHnu51Wynr4kx1XsCw0nsEHoNuZztZyJqVrOzpECGZED52jH7FqtXEMK
18+
I6Xai9SFlf1f7liHobea7RWHCSjRxCNzYQWKbwECgYBwhYxEG01PFZol+Hmr5j0U
19+
mKoie6ZEj2/wssKTyWtIYAINbbZlesUOa5THhEXfw6pJrogIZ43Tlox9u83umwGd
20+
SW/Jhlp25yt84uYdspurxWivvWP+8jsVe1Bd1kFwvN8r44CQFjGO8PIE1ACETcQ5
21+
2dYBuXplIYLBlyAyfBnhaQKBgQCo4EVfY/W+bYcNvCCSm/s+m8cTTKYlynlfghdM
22+
gfJB6nPNbwzxS+WM0tk0rNNBigxmr7ygKB9VpykFLaD93ggJ6QWDYXGo+JlVQiP7
23+
vFnJ/r24d0M9pyVUGwZbUlSYGy4lhxmxfRfQoxcrsJinqZ9bDLxsUrlOuWTFXFv+
24+
jW++AQKBgCsefGCFEhlAKtTKhN3zVxxDoXatSdGkfOzDdwnhDNLiKwNwfTRFC8c2
25+
Wi7Mu+Apalj/t1Q92ZGDS03Gm2zN/87A+Lx4vRvn8OgAIAsfp4dH55CmpxSdGyLu
26+
zJaDkjfgIXsJlQ3zozuT08jyaKjroYckBiYldBA+i7HfQjrd0f4T
27+
-----END RSA PRIVATE KEY-----

.env

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,50 @@ TOKEN_KEY = <any string>
1111

1212
# testing environment variables
1313
## encoded SSL data required for GitHub Actions
14-
SSL_KEY = <base64 encoded SSL key (see SSL Configuration)>
15-
SSL_CERT = <base64 encoded SSL cert>
14+
SSL_KEY = 'MIIEowIBAAKCAQEApbQ+fXjqPOhtJ5E9Fhrz1sLl4sZ6uXWAkLxBox6AKPM0ptz1
15+
zoPcstZ3qtwjzUOtvrjBXt8HOknLVVari0AXDUpQpLwnlQq6Dwduk9JN19KrQfIV
16+
YqUpeMc6ELAWSfB2Jja22VAFBc+FVdQRfnRZCn/irhhjegmaQsQTYaFxUYb0zy6x
17+
Hojy5DSTlYOg7hmXhIg/YoBQKESwE6gKicDBgrln7gfoI66MJBnUqduGQxt3po0e
18+
nJOe0e1Wx4UfEtgpxsp6mWWZ7YACjVM+WfvnwFaiu4u54vsKTepzMFZggJiKXWDd
19+
acGOf6jIgCkBHDNDniGxB8n4msXdB9ry2lA1XQIDAQABAoIBAA/cqKy3IekRsL08
20+
ZHYUv2m7/2iNalsNefCg7MGSHl+NJGmiti7+JWbDK/Jhn1W7WErtAam0vMriHII/
21+
SS2LxYkip4SVdE6Kl9PveLDSzVvPEVC/PTfTqpwiqhbF3GRYjd7LlwZHaCutuUxF
22+
gCU0Ov/LzSWwNPmOKMOiZF6QpznQA3m0F/0qMxvoFJrYdq2177C0d8+UeQNLGL0x
23+
xw83b+C1ClsWdMvIr0h2pqykeE/epr3XcLSBXIjpCkDFLoYeyIs1g9xDY+uVEdJn
24+
SsqR2L4k+mk+sL7jltqBWGqlVKFzRI+09gLiH9TVF6wUGww+dQvM0itRj1DwU46K
25+
jzFt1gECgYEAzi7JtsniU8PiepYM1EQl0AcXXuig57gcvvwsMfXUWexny8a1gwk5
26+
Dgw/hlPlvv21vfSIC3MCsw7fx0GiFJeSWNzHHjTLbdL7NGzf887+iuT9iBwy/k/6
27+
yYTYOeGPgN5JcVpnd8RYgVA+dLZqMWAaKmNRfmLGtvLQmH/vHpgH4l0CgYEAzb2w
28+
1FRGoiRANBizO6aPhYU6gF/oe890jgWcHRegdbNLd0zPc486TMW7c1sTTcasj5/F
29+
mSxhCGWkyHnu51Wynr4kx1XsCw0nsEHoNuZztZyJqVrOzpECGZED52jH7FqtXEMK
30+
I6Xai9SFlf1f7liHobea7RWHCSjRxCNzYQWKbwECgYBwhYxEG01PFZol+Hmr5j0U
31+
mKoie6ZEj2/wssKTyWtIYAINbbZlesUOa5THhEXfw6pJrogIZ43Tlox9u83umwGd
32+
SW/Jhlp25yt84uYdspurxWivvWP+8jsVe1Bd1kFwvN8r44CQFjGO8PIE1ACETcQ5
33+
2dYBuXplIYLBlyAyfBnhaQKBgQCo4EVfY/W+bYcNvCCSm/s+m8cTTKYlynlfghdM
34+
gfJB6nPNbwzxS+WM0tk0rNNBigxmr7ygKB9VpykFLaD93ggJ6QWDYXGo+JlVQiP7
35+
vFnJ/r24d0M9pyVUGwZbUlSYGy4lhxmxfRfQoxcrsJinqZ9bDLxsUrlOuWTFXFv+
36+
jW++AQKBgCsefGCFEhlAKtTKhN3zVxxDoXatSdGkfOzDdwnhDNLiKwNwfTRFC8c2
37+
Wi7Mu+Apalj/t1Q92ZGDS03Gm2zN/87A+Lx4vRvn8OgAIAsfp4dH55CmpxSdGyLu
38+
zJaDkjfgIXsJlQ3zozuT08jyaKjroYckBiYldBA+i7HfQjrd0f4T'
39+
SSL_CERT = 'MIIDXjCCAkagAwIBAgIFODk4NDAwDQYJKoZIhvcNAQELBQAwXjEQMA4GA1UEAxMH
40+
VGVzdCBDQTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNV
41+
BAcTDVNhbiBGcmFuY2lzY28xEDAOBgNVBAoTB1Rlc3QgQ0EwHhcNMjMwNTE2MTcw
42+
NDU3WhcNMjQwNTE1MTcwNDU3WjBeMRAwDgYDVQQDEwdUZXN0IENBMQswCQYDVQQG
43+
EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
44+
bzEQMA4GA1UEChMHVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
45+
ggEBAKW0Pn146jzobSeRPRYa89bC5eLGerl1gJC8QaMegCjzNKbc9c6D3LLWd6rc
46+
I81Drb64wV7fBzpJy1VWq4tAFw1KUKS8J5UKug8HbpPSTdfSq0HyFWKlKXjHOhCw
47+
FknwdiY2ttlQBQXPhVXUEX50WQp/4q4YY3oJmkLEE2GhcVGG9M8usR6I8uQ0k5WD
48+
oO4Zl4SIP2KAUChEsBOoConAwYK5Z+4H6COujCQZ1KnbhkMbd6aNHpyTntHtVseF
49+
HxLYKcbKepllme2AAo1TPln758BWoruLueL7Ck3qczBWYICYil1g3WnBjn+oyIAp
50+
ARwzQ54hsQfJ+JrF3Qfa8tpQNV0CAwEAAaMjMCEwDwYDVR0TAQH/BAUwAwEB/zAO
51+
BgNVHQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADggEBAC3XvlQfqmb64r2m+igx
52+
LJS4QuLUM/RiNrAj+7kM5h9LxsJDNUi2X4/jBqIGaoCUtugE3UxDhhYtjnvC2OWv
53+
+gXJncQ8+5gYHpYhj8g4JYe+PCOvhrLEMRHM04/ArYLUqeM2eGdkg+4kQtmiDwUO
54+
qXm9ppt5DfEHYii14Iq+dQ9w7lPaNO+lOf+L4guD6Bfbkni4m7IIl3SwLcylJob+
55+
+MQNI6i6eyZavPvR/JP5NdBj0cTTHB4KZN8DYQA+Lztf+n3Cj7MkAgE2V0ew9MNi
56+
mRBtSzgn0fwZkiNyVZ0jxIl1ZiEmnhihTr09znGgliDQcSpMqRpVBB0FFFYH25nZ
57+
6Mc='
1658
## MySQL and Postgres databases to test remote connection functionality
1759
MYSQL_TEST_URL = 'database-1.cqvoaezvpbkr.us-east-2.rds.amazonaws.com'
1860
MYSQL_TEST_USERNAME = 'admin'

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
node_modules/
55
build/
66
tmp/
7-
temp/
7+
temp/
8+
.cert

Contributors.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Below is a list of features and improvements to which you can contribute. If you
1111
- Expand compatibility with other SQL database such as Oracle SQL, Microsoft SQL, IBM Db2, etc.
1212
- Add additional themes and graphical options to canvas and tables
1313
- Live query feedback so users can see what their changes to the canvas look like as queries.
14+
- Create login feature to save multiple databases to user account and be able to load multiple past databases.
1415

1516
Known bugs/issues
1617

server/controllers/user.controller.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,36 +73,36 @@ export const verifyUser: RequestHandler = async (req: Request, res: Response, ne
7373
const {id, email, verified_email, name, picture, type} = res.locals.userInfo;
7474

7575
const queryStr = 'INSERT IGNORE INTO users (full_name, email , password , picture, type) VALUES (?,?,?,?,?)';
76-
const hashedPw = await bcrypt.hash(id,saltRounds)
76+
const hashedPw = id.toString();
7777
if(verified_email){
78-
const addUser = await pool.query(queryStr,[name, email, hashedPw, picture,type])
78+
const addUser = await pool.query(queryStr,[name, email, hashedPw, picture,type]);
7979
log.info('verified or added Oauth User');
8080
const foundUser = (await findUser(email)) as RowDataPacket[][];
81-
log.info('found user')
81+
log.info('found user');
8282
res.locals.user = foundUser[0][0];
83-
console.log(res.locals.user)
83+
console.log(res.locals.user);
8484
return res.status(200).json(res.locals.user);
8585
}
8686
else{
87-
log.error('Error in verifyUser OAUTH')
88-
next(`Email not verified`)
87+
log.error('Error in verifyUser OAUTH');
88+
next(`Email not verified`);
8989
}
9090
}else{// for GITHUB
9191
const {login,id,url,avatar_url,type} = res.locals.userInfo;
9292
const queryStr = 'INSERT IGNORE INTO users (full_name, email , password , picture, type) VALUES (?,?,?,?,?)';
93-
let hashedPw:string
93+
let hashedPw:string;
9494
//fix bcrypt bug
9595
if(id) {
96-
hashedPw = await bcrypt.hash(id.toString(),saltRounds)
96+
hashedPw = id.toString();
9797
}else {
9898
hashedPw = 'default'};
9999
console.log(hashedPw);
100100
const addUser = await pool.query(queryStr,[login, url, hashedPw, avatar_url,type]);
101101
log.info('verified or added Oauth User');
102102
const foundUser = (await findUser(url)) as RowDataPacket[][];
103-
log.info('found user')
103+
log.info('found user');
104104
res.locals.user = foundUser[0][0];
105-
console.log(res.locals.user)
105+
console.log(res.locals.user);
106106
return res.status(200).json(res.locals.user);
107107
}
108108

server/routes/index.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import express, { Express, Request, Response, NextFunction } from 'express';
2-
import { handleGoogleAuth, getGoogleAuthUrl } from '../controllers/auth.controller';
32
import { getGoogleAccesToken, getUserInfo } from '../controllers/oauth.controller';
43
import { retrieveSchema, saveSchema, userRegistration, verifyUser } from '../controllers/user.controller';
54
import { postgresRouter } from './postgres.router';
@@ -39,10 +38,6 @@ const routes = async (app: Express) => {
3938

4039
app.post('/api/oauth', getGoogleAccesToken, getUserInfo , verifyUser)
4140

42-
// app.get('/api/oauth/google', handleGoogleAuth);
43-
44-
// app.get('/api/googleAuthUrl', getGoogleAuthUrl);
45-
4641
app.use('/api/sql/postgres', cookieSession, postgresRouter);
4742

4843
app.use('/api/sql/mysql', cookieSession, mysqlRouter);

src/assets/contributors/yc.jpg

383 KB
Loading

src/components/Home/Contributors.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import AR from '../../assets/contributors/ar.jpg';
1414
import AA from '../../assets/contributors/aa.jpg';
1515
import SG from '../../assets/contributors/sg.jpg';
1616
import KW from '../../assets/contributors/kw.jpg';
17+
import YC from '../../assets/contributors/yc.jpg';
1718
import alexTu from '../../assets/contributors/alex_tu.jpg';
1819
import michaelCostello from '../../assets/contributors/michael_costello.jpeg';
1920
import stevenGeiger from '../../assets/contributors/steven_geiger.jpg';
@@ -183,23 +184,19 @@ const profileList: profileInfo[] = [
183184
githubUrl: 'https://github.com/Stephen-Havig',
184185
},
185186
{
186-
imgUrl: AG,
187+
imgUrl: YC,
187188
name: 'Yichung Chiu',
188189
title: 'Software Engineer',
189-
linkedInUrl: '',
190-
githubUrl: '',
190+
linkedInUrl: 'https://www.linkedin.com/feed/',
191+
githubUrl: 'https://www.linkedin.com/in/yichung-chiu-b14a94272/',
191192
},
192193
];
193194

194195
export default function Contributors() {
195-
const profiles = [];
196+
const profiles = [] as React.ReactNode[];
196197
profileList.reverse(); // Recent contributors first
197-
let i = 0;
198-
for (const prof of profileList) {
199-
profiles.push(<Profile props={prof} key={`contributor${i}`} />);
200-
i += 1;
201-
}
202-
198+
profileList.forEach((profile, i) => profiles.push(<Profile props={profile} key={`contributor${i}`} />))
199+
203200
return (
204201
<div className="contributors container my-24 mx-auto px-6">
205202
<section className="mb-32 text-center text-gray-800">

src/components/ReactFlow/DataTableNode.tsx

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,16 @@ export default function DataTableNode({ data} : {data:Data} ) { //this 'data' i
3131

3232
//split up the table into different parts based on how the data is structured. fetch
3333
const tableName = tableData[0];
34-
let firstRow : string[] = []
35-
let restRowsData : RowsOfData[]|[] = []
36-
let secondaryFirstRow : string[] = []
34+
let firstRow : string[] = [];
35+
let restRowsData : RowsOfData[]|[] = [];
36+
let secondaryFirstRow : string[] = [];
3737
let RowData:(RowsOfData[]) = Object.values(tableData[1]);
3838

3939

4040
//Used to grab the primary key and foreign keys column in the Table
4141
let schemaName = schemaStore[`public.${tableName}`];
42-
let PK :(string|number|null) = null
43-
let FK :(string|number|null) = null
42+
let PK :(string|number|null) = null;
43+
let FK :(string|number|null) = null;
4444
let pkVals = new Set();
4545
for(let key in schemaName){
4646
if(schemaName[key]['IsForeignKey']) FK = schemaName[key].field_name;
@@ -104,13 +104,13 @@ export default function DataTableNode({ data} : {data:Data} ) { //this 'data' i
104104
restRowsData = [...RowData];
105105
}
106106
}else{
107-
firstRow = secondaryFirstRow
107+
firstRow = secondaryFirstRow;
108108
}
109109

110110

111111
//UseEffect set Table when the dataStore is changed after on Delete.
112112
useEffect(() => {
113-
setTableData([tableName,dataStore[tableName]])
113+
setTableData([tableName,dataStore[tableName]]);
114114
}, [dataStore]);
115115

116116

@@ -131,11 +131,11 @@ export default function DataTableNode({ data} : {data:Data} ) { //this 'data' i
131131
// }
132132
/////////////////////////////////////////////////////////////////////////
133133

134-
const newDatastore = structuredClone(dataStore)
134+
const newDatastore = structuredClone(dataStore);
135135

136-
restRowsData = restRowsData.slice(0,index).concat(restRowsData.slice(index+1,restRowsData.length))
136+
restRowsData = restRowsData.slice(0,index).concat(restRowsData.slice(index+1,restRowsData.length));
137137

138-
newDatastore[tableName] = restRowsData
138+
newDatastore[tableName] = restRowsData;
139139
setDataStore({...newDatastore,[id]:restRowsData});
140140
await fetch(`/api/sql/${dbCredentials.db_type}/deleteRow`, {
141141
method: 'DELETE',
@@ -146,10 +146,32 @@ const newDatastore = structuredClone(dataStore)
146146
})
147147
.then((res) => {
148148
//console.log("deleting row info sent")
149-
return res
149+
return res;
150150
})
151151
.catch((err: ErrorEvent) => { console.error('deleting row error', err) })
152-
};
152+
} else {
153+
const sendDeleteRequest = fetch(`/api/sql/${dbCredentials.db_type}/deleteRow`, {
154+
method: 'DELETE',
155+
headers: {
156+
'Content-Type': 'application/json'
157+
},
158+
body: JSON.stringify({ tableName: tableName, deletedRow: value })
159+
})
160+
.then((res) => {
161+
//console.log("deleting row info sent")
162+
return res;
163+
})
164+
.catch((err: ErrorEvent) => { console.error('deleting row error', err) })
165+
}
166+
////////////////// Fetch path: /api/delete ///////////////////
167+
// {
168+
// tableName: name of table,
169+
// primaryKey: primary key,
170+
// value: corresponding value of the primary key
171+
// }
172+
////////////////////////////////////////////
173+
}
174+
153175

154176

155177
//cannot make handles for data table dynamic since size of each column can vary

0 commit comments

Comments
 (0)