diff --git a/README.md b/README.md index 396d4bf..7de4925 100644 --- a/README.md +++ b/README.md @@ -71,17 +71,19 @@ For `PATCH /api/company/:id`: | Method | Signature | Returns | | --- | --- | --- | | `add` | `(email)` | `id` | -| `getByEmail` | `(email)` | `{ id, email, picture, sub, createdAt, selectedCompanyId } \| undefined` | -| `listAllowedEmails` | `()` | `{ id, email, picture, sub, createdAt, selectedCompanyId }[]` | +| `getByEmail` | `(email)` | `{ id, email, picture, pictureBinary, sub, createdAt, selectedCompanyId } \| undefined` | +| `getById` | `(googleAuthId)` | `{ id, email, picture, pictureBinary, sub, createdAt, selectedCompanyId } \| undefined` | +| `listAllowedEmails` | `()` | `{ id, email, picture, sub, createdAt, selectedCompanyId }[]` (no `pictureBinary`) | | `removeAllowedEmail` | `(email)` | `void` | | `setSelectedCompany` | `(googleAuthId, companyId)` | `void` | -| `setProfile` | `(googleAuthId, { picture?: string \| null, sub?: string \| null })` | `void` | +| `setProfile` | `(googleAuthId, { picture?: string \| null, pictureBinary?: Buffer \| null, sub?: string \| null })` | `void` | -`picture` / `sub` come from the Google ID token (`payload.picture`, `payload.sub`). `sub` is Google’s stable subject id (unique when set). `setProfile` overwrites both columns (pass `null` to clear). Errors: `User not found`. +`picture` is the Google profile image URL (kept for change detection / re-fetch). `picturebinary` stores the downloaded image bytes (`pictureBinary` in JS, `Buffer` from mssql). `sub` is Google’s stable subject id (unique when set). `setProfile` overwrites `picture`, `pictureBinary`, and `sub` (pass `null` to clear). Non-Buffer `pictureBinary` → `Invalid pictureBinary`. Errors: `User not found`. Backend mapping: -- Login: `getByEmail(email)` then `setProfile(id, { picture, sub })` from the verified token +- Login: `getByEmail(email)` then download `payload.picture` → `setProfile(id, { picture, pictureBinary, sub })` +- `/auth/me`: `getById(googleAuthId)` → base64-encode `pictureBinary` for the response - CLI user manager: `listAllowedEmails()` / `removeAllowedEmail(email)` / `add(email)` - `PUT /api/auth/selected-company`: `setSelectedCompany(googleAuthId, companyId)` diff --git a/schema-graph.dot b/schema-graph.dot index 61ab8a2..1b5d6f3 100644 --- a/schema-graph.dot +++ b/schema-graph.dot @@ -17,7 +17,7 @@ digraph schema { meta [label=<
meta
keyNVARCHAR(255)
valueNVARCHAR(MAX)
>]; customergroup [label=<
customergroup
idINT PK
company_id?INT FK
nameNVARCHAR(255)
>]; companysetting [label=<
companysetting
idINT PK
company_idINT FK
keyNVARCHAR(255)
valueNVARCHAR(MAX)
frontendBIT
>]; - google_auth [label=<
google_auth
idINT PK
selected_company?INT FK
emailNVARCHAR(320)
picture?NVARCHAR(2048)
sub?NVARCHAR(255)
created_atDATETIME2
>]; + google_auth [label=<
google_auth
idINT PK
selected_company?INT FK
emailNVARCHAR(320)
picture?NVARCHAR(2048)
picturebinary?VARBINARY(MAX)
sub?NVARCHAR(255)
created_atDATETIME2
>]; } subgraph cluster_i18n { label="Localization"; labeljust="l"; fontsize=15; @@ -72,7 +72,7 @@ digraph schema { __legend [label=<
Legend
PKprimary key
FKforeign key
?nullable column
—FOREIGN KEY constraint
- -naming convention only, no constraint
>]; - google_auth:selected_company:sw -> company:in8:n [color="#c53030", fontcolor="#c53030", tooltip="FK_google_auth_selected_company"]; + google_auth:selected_company:w -> company:in8:n [color="#c53030", fontcolor="#c53030", tooltip="FK_google_auth_selected_company"]; category:parent_id:e -> category:id:e [color="#2f855a", fontcolor="#2f855a", tooltip="FK_category_parent"]; company:root_category_id:w -> category:in5:n [color="#2b6cb0", fontcolor="#2b6cb0", tooltip="FK_company_root_category"]; company:default_language_id:w -> language:in3:n [color="#b7791f", fontcolor="#b7791f", tooltip="FK_company_default_language"]; diff --git a/schema-graph.svg b/schema-graph.svg index 765783f..947dd0e 100644 --- a/schema-graph.svg +++ b/schema-graph.svg @@ -4,30 +4,30 @@ - + schema - + cluster_identity - -Identity & Company + +Identity & Company cluster_i18n - -Localization + +Localization cluster_catalog - -Catalog + +Catalog cluster_media - -Media + +Media cluster_commerce @@ -36,8 +36,8 @@ cluster_units - -Units + +Units @@ -81,12 +81,12 @@ - - + + -company +company id @@ -96,36 +96,36 @@ default_language_id ? -INT +INT FK root_category_id ? -INT +INT FK poscustomergroup_id -INT +INT FK taxrate ? -INT +INT FK reducedtaxrate ? -INT +INT FK taxfreerate ? -INT +INT FK @@ -136,7 +136,7 @@ numbers:se->company:n - + @@ -144,25 +144,25 @@ entity - - + + entity - - + + id INT PK - - + + customergroup_id INT FK - - + + firstname NVARCHAR(255) - - + + lastname NVARCHAR(255) @@ -195,7 +195,7 @@ entity:e->customergroup:n - + @@ -203,31 +203,31 @@ companyuser - - - -companyuser - - -id -INT -PK - - -company_id -INT -FK - - -google_auth_id -INT -FK + + + +companyuser + + +id +INT +PK + + +company_id +INT +FK + + +google_auth_id +INT +FK companyuser:se->company:n - + @@ -235,44 +235,48 @@ google_auth - - - -google_auth - - -id -INT -PK - - -selected_company -? -INT -FK - - -email -NVARCHAR(320) - - -picture? -NVARCHAR(2048) - - -sub? -NVARCHAR(255) - - -created_at -DATETIME2 + + + +google_auth + + +id +INT +PK + + +selected_company +? +INT +FK + + +email +NVARCHAR(320) + + +picture? +NVARCHAR(2048) + + +picturebinary? +VARBINARY(MAX) + + +sub? +NVARCHAR(255) + + +created_at +DATETIME2 companyuser:e->google_auth:n - - + + @@ -314,7 +318,7 @@ company:se->customergroup:n - + @@ -322,66 +326,66 @@ language - - - - - - -language - - -id -INT -PK - - -iso639 -NVARCHAR(10) + + + + + + +language + + +id +INT +PK + + +iso639 +NVARCHAR(10) company:w->language:n - - + + category - - - - - - - - -category - - -id -INT -PK - - -parent_id -? -INT -FK - - -sort -INT + + + + + + + + +category + + +id +INT +PK + + +parent_id +? +INT +FK + + +sort +INT company:w->category:n - - + + @@ -436,23 +440,23 @@ meta - - -meta - - -key -NVARCHAR(255) - - -value -NVARCHAR(MAX) + + +meta + + +key +NVARCHAR(255) + + +value +NVARCHAR(MAX) customergroup:nw->company:n - + @@ -497,271 +501,271 @@ -google_auth:sw->company:n +google_auth:w->company:n - - + + articletext - - -articletext - - -id -INT -PK - - -language_id -INT -FK - - -article_id -INT -FK - - -name -NVARCHAR(255) - - -slug -NVARCHAR(255) - - -seo? -NVARCHAR(MAX) - - -desc? -NVARCHAR(MAX) + + +articletext + + +id +INT +PK + + +language_id +INT +FK + + +article_id +INT +FK + + +name +NVARCHAR(255) + + +slug +NVARCHAR(255) + + +seo? +NVARCHAR(MAX) + + +desc? +NVARCHAR(MAX) articletext:sw->language:n - - + + article - - - - - - - - - - - - -article - - -id -INT -PK - - -brand_id -? -INT -FK - - -fatherarticle_id -? -INT -FK - - -articlelist_id -? -INT -FK - - -optionslist_id -? -INT -FK - - -unit_id -INT -FK - - -sku? -NVARCHAR(100) - - -unitamount -FLOAT - - -weight -FLOAT - - -is_father -BIT - - -fathersort -INT - - -created_at -DATETIME2 - - -updated_at -DATETIME2 + + + + + + + + + + + + +article + + +id +INT +PK + + +brand_id +? +INT +FK + + +fatherarticle_id +? +INT +FK + + +articlelist_id +? +INT +FK + + +optionslist_id +? +INT +FK + + +unit_id +INT +FK + + +sku? +NVARCHAR(100) + + +unitamount +FLOAT + + +weight +FLOAT + + +is_father +BIT + + +fathersort +INT + + +created_at +DATETIME2 + + +updated_at +DATETIME2 articletext:e->article:n - - + + unittext - - -unittext - - -id -INT -PK - - -language_id -INT -FK - - -unit_id -INT -FK - - -symbol -NVARCHAR(50) - - -name -NVARCHAR(255) + + +unittext + + +id +INT +PK + + +language_id +INT +FK + + +unit_id +INT +FK + + +symbol +NVARCHAR(50) + + +name +NVARCHAR(255) unittext:sw->language:n - - + + unit - - - - -unit - - -id -INT -PK - - -company_id -INT -FK - - -si -NVARCHAR(50) - - -basesi -NVARCHAR(50) - - -basesifactor -FLOAT + + + + +unit + + +id +INT +PK + + +company_id +INT +FK + + +si +NVARCHAR(50) + + +basesi +NVARCHAR(50) + + +basesifactor +FLOAT unittext:e->unit:n - - + + categorytext - - -categorytext - - -id -INT -PK - - -language_id -INT -FK - - -category_id -INT -FK - - -name -NVARCHAR(255) - - -slug -NVARCHAR(255) - - -seo? -NVARCHAR(MAX) - - -desc? -NVARCHAR(MAX) + + +categorytext + + +id +INT +PK + + +language_id +INT +FK + + +category_id +INT +FK + + +name +NVARCHAR(255) + + +slug +NVARCHAR(255) + + +seo? +NVARCHAR(MAX) + + +desc? +NVARCHAR(MAX) categorytext:sw->language:n - - + + @@ -769,8 +773,8 @@ categorytext:e->category:n - - + + @@ -778,39 +782,39 @@ category:e->category:e - - + + categoryarticle - - -categoryarticle - - -id -INT -PK - - -category_id -INT -FK - - -article_id -INT -FK + + +categoryarticle + + +id +INT +PK + + +category_id +INT +FK + + +article_id +INT +FK categoryarticle:sw->category:n - - + + @@ -818,105 +822,105 @@ categoryarticle:e->article:n - - + + articlelistarticle - - -articlelistarticle - - -id -INT -PK - - -article_id -INT -FK - - -articlelist_id -INT -FK - - -count -INT - - -sort -INT + + +articlelistarticle + + +id +INT +PK + + +article_id +INT +FK + + +articlelist_id +INT +FK + + +count +INT + + +sort +INT articlelistarticle:se->article:n - - + + articlelist - - - - -articlelist - - -id -INT -PK + + + + +articlelist + + +id +INT +PK articlelistarticle:se->articlelist:n - - + + brand - - - - -brand - - -id -INT -PK - - -company_id -INT -FK - - -name -NVARCHAR(255) - - -gsprpinfo? -NVARCHAR(MAX) + + + + +brand + + +id +INT +PK + + +company_id +INT +FK + + +name +NVARCHAR(255) + + +gsprpinfo? +NVARCHAR(MAX) brand:e->company:n - + @@ -924,30 +928,30 @@ articleean - - -articleean - - -id -INT -PK - - -article_id -INT -FK - - -ean -NVARCHAR(50) + + +articleean + + +id +INT +PK + + +article_id +INT +FK + + +ean +NVARCHAR(50) articleean:se->article:n - - + + @@ -955,8 +959,8 @@ article:sw->brand:n - - + + @@ -964,8 +968,8 @@ article:e->article:e - - + + @@ -973,31 +977,31 @@ article:se->articlelist:n - - + + optionslist - - - - -optionslist - - -id -INT -PK + + + + +optionslist + + +id +INT +PK article:se->optionslist:n - - + + @@ -1005,48 +1009,48 @@ article:e->unit:n - - + + articlehistory - - -articlehistory - - -id -INT -PK - - -article_id -INT -FK - - -companyuser_id -? -INT -FK - - -date -DATETIME2 - - -text -NVARCHAR(MAX) + + +articlehistory + + +id +INT +PK + + +article_id +INT +FK + + +companyuser_id +? +INT +FK + + +date +DATETIME2 + + +text +NVARCHAR(MAX) articlehistory:e->companyuser:n - - + + @@ -1054,51 +1058,51 @@ articlehistory:sw->article:n - - + + optionslistarticle - - -optionslistarticle - - -id -INT -PK - - -article_id -INT -FK - - -optionslist_id -INT -FK - - -count -INT - - -sort -INT - - -mandated -BIT + + +optionslistarticle + + +id +INT +PK + + +article_id +INT +FK + + +optionslist_id +INT +FK + + +count +INT + + +sort +INT + + +mandated +BIT optionslistarticle:sw->article:n - - + + @@ -1106,142 +1110,142 @@ optionslistarticle:sw->optionslist:n - - + + categorypic - - -categorypic - - -id -INT -PK - - -pic_id -INT -FK - - -category_id -INT -FK - - -sort -INT + + +categorypic + + +id +INT +PK + + +pic_id +INT +FK + + +category_id +INT +FK + + +sort +INT categorypic:se->category:n - - + + pic - - - - - -pic - - -id -INT -PK - - -w -INT - - -h -INT - - -size -BIGINT - - -hash -NVARCHAR(64) - - -thumbw -INT - - -thumbh -INT - - -thumbhash -NVARCHAR(64) - - -source -NVARCHAR(MAX) - - -fullbinary -VARBINARY(MAX) - - -thumbbinary -VARBINARY(MAX) + + + + + +pic + + +id +INT +PK + + +w +INT + + +h +INT + + +size +BIGINT + + +hash +NVARCHAR(64) + + +thumbw +INT + + +thumbh +INT + + +thumbhash +NVARCHAR(64) + + +source +NVARCHAR(MAX) + + +fullbinary +VARBINARY(MAX) + + +thumbbinary +VARBINARY(MAX) categorypic:se->pic:n - - + + articlepic - - -articlepic - - -id -INT -PK - - -pic_id -INT -FK - - -article_id -INT -FK - - -sort -INT + + +articlepic + + +id +INT +PK + + +pic_id +INT +FK + + +article_id +INT +FK + + +sort +INT articlepic:e->article:n - - + + @@ -1249,43 +1253,43 @@ articlepic:sw->pic:n - - + + brandpic - - -brandpic - - -id -INT -PK - - -pic_id -INT -FK - - -brand_id -INT -FK - - -sort -INT + + +brandpic + + +id +INT +PK + + +pic_id +INT +FK + + +brand_id +INT +FK + + +sort +INT brandpic:e->brand:n - - + + @@ -1293,43 +1297,43 @@ brandpic:sw->pic:n - - + + price - - -price - - + + +price + + id INT PK - - + + article_id INT FK - - + + customergroup_id INT FK - - + + tax_id INT FK - - + + minmultiplier DECIMAL(18,4) - - + + net FLOAT @@ -1346,8 +1350,8 @@ price:w->article:n - - + + @@ -1388,57 +1392,57 @@ manifestarticle - - + + manifestarticle - - -id + + +id INT PK - - -article_id + + +article_id ? INT FK - - -tax_id + + +tax_id INT FK - - -manifest_id + + +manifest_id INT FK - - -amount + + +amount FLOAT - - -net + + +net FLOAT - - -transactedamount + + +transactedamount FLOAT - - -comment? + + +comment? NVARCHAR(MAX) - - -rebate + + +rebate FLOAT manifestarticle:w->article:n - - + + @@ -1446,7 +1450,7 @@ manifestarticle:sw->tax:n - + @@ -1455,7 +1459,7 @@ manifestarticle:sw->manifest:n - + @@ -1464,7 +1468,7 @@ unit:e->company:n - + @@ -1472,70 +1476,70 @@ posterminalcategory - - -posterminalcategory - - -id -INT -PK - - -posterminal_id -INT -FK - - -category_id -INT -FK + + +posterminalcategory + + +id +INT +PK + + +posterminal_id +INT +FK + + +category_id +INT +FK posterminalcategory:se->category:n - - + + posterminal - - - -posterminal - - -id -INT -PK - - -company_id -INT -FK - - -name -NVARCHAR(255) - - -token? -NVARCHAR(255) - - -pairingpin -NVARCHAR(50) + + + +posterminal + + +id +INT +PK + + +company_id +INT +FK + + +name +NVARCHAR(255) + + +token? +NVARCHAR(255) + + +pairingpin +NVARCHAR(50) posterminalcategory:se->posterminal:n - - + + @@ -1543,38 +1547,38 @@ posterminal:e->company:n - - + + __legend - - - -Legend - -PK - -primary key - -FK - -foreign key - -? - -nullable column - -— - -FOREIGN KEY constraint - -- - - -naming convention only, no constraint + + + +Legend + +PK + +primary key + +FK + +foreign key + +? + +nullable column + +— + +FOREIGN KEY constraint + +- - + +naming convention only, no constraint diff --git a/scripts/test-api.js b/scripts/test-api.js index 532f98f..2fcc5c0 100644 --- a/scripts/test-api.js +++ b/scripts/test-api.js @@ -268,6 +268,7 @@ const MENU = { items: [ { value: 'google_auth.add', label: 'add', hint: 'email → id' }, { value: 'google_auth.getByEmail', label: 'getByEmail', hint: 'email → row' }, + { value: 'google_auth.getById', label: 'getById', hint: 'googleAuthId → row' }, { value: 'google_auth.listAllowedEmails', label: 'listAllowedEmails', hint: '→ rows' }, { value: 'google_auth.removeAllowedEmail', label: 'removeAllowedEmail', hint: 'email' }, { value: 'google_auth.setSelectedCompany', label: 'setSelectedCompany', hint: 'googleAuthId, companyId' }, @@ -1106,6 +1107,11 @@ async function runMethod(client, action) { if (email == null) return; return ['row', await client.google_auth.getByEmail(email)]; } + case 'google_auth.getById': { + const id = await askInt('Auth id'); + if (id == null) return; + return ['row', await client.google_auth.getById(Number(id))]; + } case 'google_auth.listAllowedEmails': { return ['rows', await client.google_auth.listAllowedEmails()]; } diff --git a/src/index.js b/src/index.js index a6cb526..2e0694b 100644 --- a/src/index.js +++ b/src/index.js @@ -58,6 +58,7 @@ import taxEnsure from './store/tax/ensure.js'; import taxList from './store/tax/list.js'; import googleAuthAdd from './store/google_auth/add.js'; import googleAuthGetByEmail from './store/google_auth/getByEmail.js'; +import googleAuthGetById from './store/google_auth/getById.js'; import googleAuthListAllowedEmails from './store/google_auth/listAllowedEmails.js'; import googleAuthRemoveAllowedEmail from './store/google_auth/removeAllowedEmail.js'; import googleAuthSetSelectedCompany from './store/google_auth/setSelectedCompany.js'; @@ -162,6 +163,7 @@ export default class LibDashStore { this.google_auth = { add: googleAuthAdd(pool), getByEmail: googleAuthGetByEmail(pool), + getById: googleAuthGetById(pool), listAllowedEmails: googleAuthListAllowedEmails(pool), removeAllowedEmail: googleAuthRemoveAllowedEmail(pool), setSelectedCompany: googleAuthSetSelectedCompany(pool), diff --git a/src/schema.js b/src/schema.js index 49fa134..85a3ee7 100644 --- a/src/schema.js +++ b/src/schema.js @@ -22,6 +22,7 @@ BEGIN id INT IDENTITY(1,1) PRIMARY KEY, email NVARCHAR(320) NOT NULL, picture NVARCHAR(2048) NULL, + picturebinary VARBINARY(MAX) NULL, sub NVARCHAR(255) NULL, created_at DATETIME2 NOT NULL CONSTRAINT DF_google_auth_created_at DEFAULT SYSUTCDATETIME(), selected_company INT NULL, diff --git a/src/sql/google_auth.js b/src/sql/google_auth.js index 089e2da..9fc7319 100644 --- a/src/sql/google_auth.js +++ b/src/sql/google_auth.js @@ -4,18 +4,30 @@ OUTPUT INSERTED.id VALUES (@email) `; -export const GET_BY_EMAIL = ` -SELECT +export const GOOGLE_AUTH_COLUMNS = ` id, email, picture, + picturebinary AS pictureBinary, sub, created_at AS createdAt, selected_company AS selectedCompanyId +`; + +export const GET_BY_EMAIL = ` +SELECT +${GOOGLE_AUTH_COLUMNS} FROM google_auth WHERE email = @email `; +export const GET_BY_ID = ` +SELECT +${GOOGLE_AUTH_COLUMNS} +FROM google_auth +WHERE id = @googleAuthId +`; + export const LIST_ALLOWED_EMAILS = ` SELECT id, @@ -41,6 +53,6 @@ WHERE id = @googleAuthId export const SET_PROFILE = ` UPDATE google_auth -SET picture = @picture, sub = @sub +SET picture = @picture, picturebinary = @pictureBinary, sub = @sub WHERE id = @googleAuthId `; diff --git a/src/store/google_auth/getById.js b/src/store/google_auth/getById.js new file mode 100644 index 0000000..866e768 --- /dev/null +++ b/src/store/google_auth/getById.js @@ -0,0 +1,12 @@ +import { GET_BY_ID } from '../../sql/google_auth.js'; + +export default function getById(pool) { + return async (googleAuthId) => { + const result = await pool + .request() + .input('googleAuthId', googleAuthId) + .query(GET_BY_ID); + + return result.recordset[0]; + }; +} diff --git a/src/store/google_auth/setProfile.js b/src/store/google_auth/setProfile.js index 4c1593a..b058c09 100644 --- a/src/store/google_auth/setProfile.js +++ b/src/store/google_auth/setProfile.js @@ -1,11 +1,19 @@ import { SET_PROFILE } from '../../sql/google_auth.js'; export default function setProfile(pool) { - return async (googleAuthId, { picture = null, sub = null } = {}) => { + return async ( + googleAuthId, + { picture = null, pictureBinary = null, sub = null } = {}, + ) => { + if (pictureBinary != null && !Buffer.isBuffer(pictureBinary)) { + throw new Error('Invalid pictureBinary'); + } + const result = await pool .request() .input('googleAuthId', googleAuthId) .input('picture', picture) + .input('pictureBinary', pictureBinary) .input('sub', sub) .query(SET_PROFILE);