Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
v2s-datapod
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
nodes
v2s-datapod
Commits
ce7eeeca
Commit
ce7eeeca
authored
Dec 24, 2023
by
poka
Browse files
Options
Downloads
Patches
Plain Diff
enh: refactor update_profile with types
parent
63ba7f15
No related branches found
No related tags found
No related merge requests found
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
index.ts
+34
-23
34 additions, 23 deletions
index.ts
lib/signature_verify.ts
+53
-30
53 additions, 30 deletions
lib/signature_verify.ts
lib/types.ts
+14
-0
14 additions, 0 deletions
lib/types.ts
lib/update_profile.ts
+76
-48
76 additions, 48 deletions
lib/update_profile.ts
lib/utils.ts
+73
-45
73 additions, 45 deletions
lib/utils.ts
with
250 additions
and
146 deletions
index.ts
+
34
−
23
View file @
ce7eeeca
import
{
Application
,
Context
,
Router
}
from
"
https://deno.land/x/oak@v12.6.1/mod.ts
"
;
import
{
Application
,
Context
,
Router
,
}
from
"
https://deno.land/x/oak@v12.6.1/mod.ts
"
;
import
{
Client
}
from
"
https://deno.land/x/postgres@v0.17.0/mod.ts
"
;
import
{
load
}
from
"
https://deno.land/std@0.209.0/dotenv/mod.ts
"
;
import
{
updateProfile
}
from
"
./lib/update_profile.ts
"
;
import
{
isProfilesTableEmpty
,
runCsplusImport
,
waitForTableCreation
}
from
"
./lib/utils.ts
"
;
import
{
isProfilesTableEmpty
,
runCsplusImport
,
waitForTableCreation
,
}
from
"
./lib/utils.ts
"
;
let
dbUser
,
dbDatabase
,
dbPassword
,
dbHostname
,
importCsplusData
;
const
dbPort
=
5432
;
...
...
@@ -32,24 +40,27 @@ const client = new Client({
port
:
dbPort
,
});
await
client
.
connect
()
await
client
.
connect
()
;
const
app
=
new
Application
();
const
router
=
new
Router
();
// Wait for table creation before continue
await
waitForTableCreation
(
client
,
'
public.profiles
'
);
await
waitForTableCreation
(
client
,
"
public.profiles
"
);
// Import Cs+ data
const
profilesEmpty
=
await
isProfilesTableEmpty
(
client
);
if
(
profilesEmpty
&&
importCsplusData
)
{
await
runCsplusImport
(
isProduction
)
await
runCsplusImport
(
isProduction
)
;
}
// Manage routes
router
.
post
(
"
/update-profile-data
"
,
async
(
ctx
:
Context
)
=>
await
updateProfile
(
ctx
,
client
));
router
.
post
(
"
/update-profile-data
"
,
async
(
ctx
:
Context
)
=>
await
updateProfile
(
ctx
,
client
),
);
app
.
use
(
router
.
routes
());
app
.
use
(
router
.
allowedMethods
());
console
.
log
(
"
\n
Datapod is started
"
)
console
.
log
(
"
\n
Datapod is started
"
)
;
await
app
.
listen
({
port
:
3000
});
This diff is collapsed.
Click to expand it.
lib/signature_verify.ts
+
53
−
30
View file @
ce7eeeca
import
{
signatureVerify
,
base64Decode
}
from
'
https://deno.land/x/polkadot@0.2.44/util-crypto/mod.ts
'
;
import
{
base64Decode
,
signatureVerify
,
}
from
"
https://deno.land/x/polkadot@0.2.44/util-crypto/mod.ts
"
;
import
{
Profile
}
from
"
./types.ts
"
;
export
enum
SignatureResponse
{
valid
,
invalidHash
,
invalidSignature
invalidSignature
,
}
export
async
function
verifySignature
(
address
:
string
,
signatureBase64
:
string
,
hash
:
string
,
playload
:
string
):
Promise
<
SignatureResponse
>
{
export
async
function
verifySignature
(
bodyValue
:
Profile
,
):
Promise
<
SignatureResponse
>
{
const
{
description
,
avatarBase64
,
geoloc
,
title
,
city
,
socials
}
=
bodyValue
;
const
payload
=
JSON
.
stringify
({
description
,
avatarBase64
,
geoloc
,
title
,
city
,
socials
,
});
try
{
const
hashVerify
=
await
createHashedMessage
(
p
l
ayload
);
if
(
hash
!=
hashVerify
)
{
console
.
error
(
'
hash documents is invalid
'
)
const
hashVerify
=
await
createHashedMessage
(
payload
);
if
(
bodyValue
.
hash
!=
hashVerify
)
{
console
.
error
(
"
hash documents is invalid
"
);
return
SignatureResponse
.
invalidHash
;
}
const
messageUint8Array
=
new
TextEncoder
().
encode
(
hash
);
const
signature
=
base64Decode
(
signatureBase64
);
const
signedMessage
=
signatureVerify
(
messageUint8Array
,
signature
,
address
)
const
messageUint8Array
=
new
TextEncoder
().
encode
(
bodyValue
.
hash
);
const
signature
=
base64Decode
(
bodyValue
.
signature
);
const
signedMessage
=
signatureVerify
(
messageUint8Array
,
signature
,
bodyValue
.
address
,
);
return
signedMessage
.
isValid
?
SignatureResponse
.
valid
:
SignatureResponse
.
invalidSignature
;
return
signedMessage
.
isValid
?
SignatureResponse
.
valid
:
SignatureResponse
.
invalidSignature
;
}
catch
(
error
)
{
console
.
error
(
'
Signature verification failed:
'
,
error
);
console
.
error
(
"
Signature verification failed:
"
,
error
);
throw
new
Error
(
`Cannot verify signature`
);
}
}
...
...
@@ -31,11 +53,12 @@ async function createHashedMessage(messageToSign: string): Promise<string> {
const
data
=
encoder
.
encode
(
messageToSign
);
// Calculate SHA-256
const
hashBuffer
=
await
crypto
.
subtle
.
digest
(
'
SHA-256
'
,
data
);
const
hashBuffer
=
await
crypto
.
subtle
.
digest
(
"
SHA-256
"
,
data
);
const
hashArray
=
Array
.
from
(
new
Uint8Array
(
hashBuffer
));
// Convert to hexa
const
hashHex
=
hashArray
.
map
(
byte
=>
byte
.
toString
(
16
).
padStart
(
2
,
'
0
'
)).
join
(
''
).
toUpperCase
();
const
hashHex
=
hashArray
.
map
((
byte
)
=>
byte
.
toString
(
16
).
padStart
(
2
,
"
0
"
))
.
join
(
""
).
toUpperCase
();
return
hashHex
;
}
This diff is collapsed.
Click to expand it.
lib/types.ts
0 → 100644
+
14
−
0
View file @
ce7eeeca
type
Geolocation
=
{
latitude
:
number
|
null
;
longitude
:
number
|
null
};
type
SocialInfo
=
{
url
:
string
;
type
:
string
};
export
type
Profile
=
{
address
:
string
;
hash
:
string
;
signature
:
string
;
avatarBase64
?:
string
|
null
;
description
?:
string
;
geoloc
?:
Geolocation
;
title
?:
string
;
city
?:
string
;
socials
?:
SocialInfo
[];
};
This diff is collapsed.
Click to expand it.
lib/update_profile.ts
+
76
−
48
View file @
ce7eeeca
...
...
@@ -2,30 +2,43 @@ import { Context } from "https://deno.land/x/oak@v12.6.1/context.ts";
import
{
Client
}
from
"
https://deno.land/x/postgres@v0.17.0/client.ts
"
;
import
{
SignatureResponse
,
verifySignature
}
from
"
./signature_verify.ts
"
;
import
{
convertBase64ToBytea
}
from
"
./utils.ts
"
;
import
{
Profile
}
from
"
./types.ts
"
;
export
async
function
updateProfile
(
ctx
:
Context
,
client
:
Client
)
{
try
{
const
body
=
(
await
ctx
.
request
.
body
().
value
)
;
const
bodyValue
=
body
.
variables
?
body
.
variables
:
(
body
.
input
?
body
.
input
:
{}
)
;
const
{
address
,
hash
,
signature
,
avatarBase64
,
description
,
geoloc
,
title
,
city
,
socials
}
=
bodyValue
;
const
body
=
await
ctx
.
request
.
body
().
value
;
const
profile
:
Profile
=
body
.
variables
||
body
.
input
||
{};
const
socialJson
=
JSON
.
stringify
(
profile
.
socials
)
;
const
socialJson
=
JSON
.
stringify
(
socials
);
// Validate input
if
(
!
profile
.
address
||
!
profile
.
hash
||
!
profile
.
signature
)
{
ctx
.
response
.
status
=
400
;
ctx
.
response
.
body
=
{
success
:
false
,
message
:
"
Address, hash, and signature are required.
"
,
};
return
;
}
// Verify signature
const
playload
=
JSON
.
stringify
({
description
,
avatarBase64
,
geoloc
,
title
,
city
,
socials
});
const
signatureResult
=
await
verifySignature
(
address
,
signature
,
hash
,
playload
);
const
signatureResult
=
await
verifySignature
(
profile
);
if
(
signatureResult
!=
SignatureResponse
.
valid
)
{
ctx
.
response
.
status
=
401
;
console
.
error
(
'
Invalid signature:
'
+
SignatureResponse
[
signatureResult
])
ctx
.
response
.
body
=
{
success
:
false
,
message
:
'
Invalid signature:
'
+
SignatureResponse
[
signatureResult
]};
console
.
error
(
"
Invalid signature:
"
+
SignatureResponse
[
signatureResult
]);
ctx
.
response
.
body
=
{
success
:
false
,
message
:
"
Invalid signature:
"
+
SignatureResponse
[
signatureResult
],
};
return
;
}
console
.
log
(
'
Signature is valid
'
)
console
.
log
(
"
Signature is valid
"
);
// Convert avatar to bytes
const
avatarBytea
=
avatarBase64
?
convertBase64ToBytea
(
avatarBase64
)
:
null
;
const
avatarBytea
=
convertBase64ToBytea
(
profile
.
avatarBase64
,
);
//
Update the user profile
//
Prepare and execute database query
const
query
=
`
INSERT INTO profiles (address, description, avatar, geoloc, title, city, socials, created_at, updated_at)
VALUES ($1, $2, $3, point($4, $5), $6, $7, $8, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
...
...
@@ -42,22 +55,37 @@ export async function updateProfile(ctx: Context, client: Client) {
try
{
await
client
.
queryObject
({
text
:
query
,
args
:
[
address
,
description
,
avatarBytea
,
geoloc
?
geoloc
[
"
latitude
"
]
:
null
,
geoloc
?
geoloc
[
"
longitude
"
]
:
null
,
title
,
city
,
socialJson
],
args
:
[
profile
.
address
,
profile
.
description
,
avatarBytea
,
profile
.
geoloc
?.
latitude
,
profile
.
geoloc
?.
longitude
,
profile
.
title
,
profile
.
city
,
socialJson
,
],
});
console
.
log
(
`Profile
${
address
}
has been updated`
);
}
catch
(
error
)
{
throw
error
;
}
// Success response
ctx
.
response
.
status
=
200
;
console
.
log
(
`Profile
${
profile
.
address
}
has been updated`
);
ctx
.
response
.
body
=
{
success
:
true
,
message
:
`Profile
${
address
}
has been updated`
message
:
`Profile
${
profile
.
address
}
has been updated`
,
};
}
catch
(
error
)
{
console
.
error
(
"
Database error in updating profile:
"
,
error
);
ctx
.
response
.
status
=
500
;
ctx
.
response
.
body
=
{
success
:
false
,
message
:
"
Internal server error :
"
+
error
,
};
}
}
catch
(
error
)
{
console
.
error
(
'
Error updating profile:
'
,
error
);
console
.
error
(
"
Error updating profile:
"
,
error
);
ctx
.
response
.
status
=
500
;
ctx
.
response
.
body
=
{
success
:
false
,
message
:
'
Error updating user
'
};
ctx
.
response
.
body
=
{
success
:
false
,
message
:
"
Error updating user:
"
+
error
,
};
}
}
This diff is collapsed.
Click to expand it.
lib/utils.ts
+
73
−
45
View file @
ce7eeeca
import
{
Client
}
from
"
https://deno.land/x/postgres@v0.17.0/client.ts
"
;
export
function
convertBase64ToBytea
(
base64String
:
string
):
Uint8Array
{
export
function
convertBase64ToBytea
(
base64String
:
string
|
null
|
undefined
,
):
Uint8Array
|
null
{
if
(
base64String
==
null
||
base64String
==
undefined
)
{
return
null
;
}
// Remove the MIME type prefix from the base64 string, if present
const
base64Data
=
base64String
.
split
(
'
,
'
)[
1
]
||
base64String
;
const
base64Data
=
base64String
.
split
(
"
,
"
)[
1
]
||
base64String
;
// Convert the base64 string to a binary string
const
binaryString
=
atob
(
base64Data
);
...
...
@@ -17,23 +23,36 @@ export function convertBase64ToBytea(base64String: string): Uint8Array {
}
export
async
function
runCsplusImport
(
isProduction
:
boolean
)
{
const
command
=
new
Deno
.
Command
(
"
./migrate_csplus/target/release/migrate_csplus
"
,
{
const
command
=
new
Deno
.
Command
(
"
./migrate_csplus/target/release/migrate_csplus
"
,
{
env
:
{
"
PRODUCTION
"
:
isProduction
.
toString
()
},
stdout
:
"
piped
"
,
stderr
:
"
piped
"
,
});
},
);
const
process
=
command
.
spawn
();
process
.
stdout
.
pipeTo
(
Deno
.
stdout
.
writable
,
{
preventClose
:
true
,
preventCancel
:
true
,
preventAbort
:
true
});
process
.
stderr
.
pipeTo
(
Deno
.
stderr
.
writable
,
{
preventClose
:
true
,
preventCancel
:
true
,
preventAbort
:
true
});
process
.
stdout
.
pipeTo
(
Deno
.
stdout
.
writable
,
{
preventClose
:
true
,
preventCancel
:
true
,
preventAbort
:
true
,
});
process
.
stderr
.
pipeTo
(
Deno
.
stderr
.
writable
,
{
preventClose
:
true
,
preventCancel
:
true
,
preventAbort
:
true
,
});
await
process
.
status
;
console
.
log
(
"
End of Cs+ data import
"
)
console
.
log
(
"
End of Cs+ data import
"
)
;
}
export
async
function
isProfilesTableEmpty
(
client
:
Client
):
Promise
<
boolean
>
{
const
result
=
await
client
.
queryObject
<
{
count
:
bigint
}
>
(
'
SELECT COUNT(*) FROM public.profiles
'
);
const
result
=
await
client
.
queryObject
<
{
count
:
bigint
}
>
(
"
SELECT COUNT(*) FROM public.profiles
"
,
);
return
result
.
rows
[
0
].
count
===
0
n
;
}
...
...
@@ -41,9 +60,14 @@ interface TableCheckResult {
to_regclass
:
string
|
null
;
}
async
function
checkTableExists
(
client
:
Client
,
tableName
:
string
):
Promise
<
boolean
>
{
async
function
checkTableExists
(
client
:
Client
,
tableName
:
string
,
):
Promise
<
boolean
>
{
try
{
const
result
=
await
client
.
queryObject
<
TableCheckResult
>
(
`SELECT to_regclass('
${
tableName
}
')`
);
const
result
=
await
client
.
queryObject
<
TableCheckResult
>
(
`SELECT to_regclass('
${
tableName
}
')`
,
);
return
result
.
rows
[
0
].
to_regclass
!==
null
;
}
catch
(
error
)
{
console
.
error
(
"
Error checking table existence:
"
,
error
);
...
...
@@ -51,10 +75,14 @@ async function checkTableExists(client: Client, tableName: string): Promise<bool
}
}
function
delay
(
ms
:
number
):
Promise
<
void
>
{
return
new
Promise
(
resolve
=>
setTimeout
(
resolve
,
ms
));
return
new
Promise
(
(
resolve
)
=>
setTimeout
(
resolve
,
ms
));
}
export
async
function
waitForTableCreation
(
client
:
Client
,
tableName
:
string
,
maxAttempts
=
10
)
{
export
async
function
waitForTableCreation
(
client
:
Client
,
tableName
:
string
,
maxAttempts
=
10
,
)
{
let
attempts
=
0
;
while
(
attempts
<
maxAttempts
)
{
if
(
await
checkTableExists
(
client
,
tableName
))
{
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
sign in
to comment