2874 lines
75 KiB
HTML
2874 lines
75 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<html>
|
|||
|
|
<head>
|
|||
|
|
<title>notes.md</title>
|
|||
|
|
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
|||
|
|
|
|||
|
|
<style>
|
|||
|
|
/* https://github.com/microsoft/vscode/blob/master/extensions/markdown-language-features/media/markdown.css */
|
|||
|
|
/*---------------------------------------------------------------------------------------------
|
|||
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|||
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|||
|
|
*--------------------------------------------------------------------------------------------*/
|
|||
|
|
|
|||
|
|
body {
|
|||
|
|
font-family: var(--vscode-markdown-font-family, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif);
|
|||
|
|
font-size: var(--vscode-markdown-font-size, 14px);
|
|||
|
|
padding: 0 26px;
|
|||
|
|
line-height: var(--vscode-markdown-line-height, 22px);
|
|||
|
|
word-wrap: break-word;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#code-csp-warning {
|
|||
|
|
position: fixed;
|
|||
|
|
top: 0;
|
|||
|
|
right: 0;
|
|||
|
|
color: white;
|
|||
|
|
margin: 16px;
|
|||
|
|
text-align: center;
|
|||
|
|
font-size: 12px;
|
|||
|
|
font-family: sans-serif;
|
|||
|
|
background-color:#444444;
|
|||
|
|
cursor: pointer;
|
|||
|
|
padding: 6px;
|
|||
|
|
box-shadow: 1px 1px 1px rgba(0,0,0,.25);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#code-csp-warning:hover {
|
|||
|
|
text-decoration: none;
|
|||
|
|
background-color:#007acc;
|
|||
|
|
box-shadow: 2px 2px 2px rgba(0,0,0,.25);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
body.scrollBeyondLastLine {
|
|||
|
|
margin-bottom: calc(100vh - 22px);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
body.showEditorSelection .code-line {
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
body.showEditorSelection .code-active-line:before,
|
|||
|
|
body.showEditorSelection .code-line:hover:before {
|
|||
|
|
content: "";
|
|||
|
|
display: block;
|
|||
|
|
position: absolute;
|
|||
|
|
top: 0;
|
|||
|
|
left: -12px;
|
|||
|
|
height: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
body.showEditorSelection li.code-active-line:before,
|
|||
|
|
body.showEditorSelection li.code-line:hover:before {
|
|||
|
|
left: -30px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-light.showEditorSelection .code-active-line:before {
|
|||
|
|
border-left: 3px solid rgba(0, 0, 0, 0.15);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-light.showEditorSelection .code-line:hover:before {
|
|||
|
|
border-left: 3px solid rgba(0, 0, 0, 0.40);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-light.showEditorSelection .code-line .code-line:hover:before {
|
|||
|
|
border-left: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-dark.showEditorSelection .code-active-line:before {
|
|||
|
|
border-left: 3px solid rgba(255, 255, 255, 0.4);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-dark.showEditorSelection .code-line:hover:before {
|
|||
|
|
border-left: 3px solid rgba(255, 255, 255, 0.60);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-dark.showEditorSelection .code-line .code-line:hover:before {
|
|||
|
|
border-left: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-high-contrast.showEditorSelection .code-active-line:before {
|
|||
|
|
border-left: 3px solid rgba(255, 160, 0, 0.7);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-high-contrast.showEditorSelection .code-line:hover:before {
|
|||
|
|
border-left: 3px solid rgba(255, 160, 0, 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-high-contrast.showEditorSelection .code-line .code-line:hover:before {
|
|||
|
|
border-left: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
img {
|
|||
|
|
max-width: 100%;
|
|||
|
|
max-height: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
a {
|
|||
|
|
text-decoration: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
a:hover {
|
|||
|
|
text-decoration: underline;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
a:focus,
|
|||
|
|
input:focus,
|
|||
|
|
select:focus,
|
|||
|
|
textarea:focus {
|
|||
|
|
outline: 1px solid -webkit-focus-ring-color;
|
|||
|
|
outline-offset: -1px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
hr {
|
|||
|
|
border: 0;
|
|||
|
|
height: 2px;
|
|||
|
|
border-bottom: 2px solid;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
h1 {
|
|||
|
|
padding-bottom: 0.3em;
|
|||
|
|
line-height: 1.2;
|
|||
|
|
border-bottom-width: 1px;
|
|||
|
|
border-bottom-style: solid;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
h1, h2, h3 {
|
|||
|
|
font-weight: normal;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
table {
|
|||
|
|
border-collapse: collapse;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
table > thead > tr > th {
|
|||
|
|
text-align: left;
|
|||
|
|
border-bottom: 1px solid;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
table > thead > tr > th,
|
|||
|
|
table > thead > tr > td,
|
|||
|
|
table > tbody > tr > th,
|
|||
|
|
table > tbody > tr > td {
|
|||
|
|
padding: 5px 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
table > tbody > tr + tr > td {
|
|||
|
|
border-top: 1px solid;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
blockquote {
|
|||
|
|
margin: 0 7px 0 5px;
|
|||
|
|
padding: 0 16px 0 10px;
|
|||
|
|
border-left-width: 5px;
|
|||
|
|
border-left-style: solid;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
code {
|
|||
|
|
font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
|
|||
|
|
font-size: 1em;
|
|||
|
|
line-height: 1.357em;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
body.wordWrap pre {
|
|||
|
|
white-space: pre-wrap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pre:not(.hljs),
|
|||
|
|
pre.hljs code > div {
|
|||
|
|
padding: 16px;
|
|||
|
|
border-radius: 3px;
|
|||
|
|
overflow: auto;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pre code {
|
|||
|
|
color: var(--vscode-editor-foreground);
|
|||
|
|
tab-size: 4;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** Theming */
|
|||
|
|
|
|||
|
|
.vscode-light pre {
|
|||
|
|
background-color: rgba(220, 220, 220, 0.4);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-dark pre {
|
|||
|
|
background-color: rgba(10, 10, 10, 0.4);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-high-contrast pre {
|
|||
|
|
background-color: rgb(0, 0, 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-high-contrast h1 {
|
|||
|
|
border-color: rgb(0, 0, 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-light table > thead > tr > th {
|
|||
|
|
border-color: rgba(0, 0, 0, 0.69);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-dark table > thead > tr > th {
|
|||
|
|
border-color: rgba(255, 255, 255, 0.69);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-light h1,
|
|||
|
|
.vscode-light hr,
|
|||
|
|
.vscode-light table > tbody > tr + tr > td {
|
|||
|
|
border-color: rgba(0, 0, 0, 0.18);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vscode-dark h1,
|
|||
|
|
.vscode-dark hr,
|
|||
|
|
.vscode-dark table > tbody > tr + tr > td {
|
|||
|
|
border-color: rgba(255, 255, 255, 0.18);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
</style>
|
|||
|
|
|
|||
|
|
<style>
|
|||
|
|
/* Tomorrow Theme */
|
|||
|
|
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
|
|||
|
|
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
|
|||
|
|
|
|||
|
|
/* Tomorrow Comment */
|
|||
|
|
.hljs-comment,
|
|||
|
|
.hljs-quote {
|
|||
|
|
color: #8e908c;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Tomorrow Red */
|
|||
|
|
.hljs-variable,
|
|||
|
|
.hljs-template-variable,
|
|||
|
|
.hljs-tag,
|
|||
|
|
.hljs-name,
|
|||
|
|
.hljs-selector-id,
|
|||
|
|
.hljs-selector-class,
|
|||
|
|
.hljs-regexp,
|
|||
|
|
.hljs-deletion {
|
|||
|
|
color: #c82829;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Tomorrow Orange */
|
|||
|
|
.hljs-number,
|
|||
|
|
.hljs-built_in,
|
|||
|
|
.hljs-builtin-name,
|
|||
|
|
.hljs-literal,
|
|||
|
|
.hljs-type,
|
|||
|
|
.hljs-params,
|
|||
|
|
.hljs-meta,
|
|||
|
|
.hljs-link {
|
|||
|
|
color: #f5871f;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Tomorrow Yellow */
|
|||
|
|
.hljs-attribute {
|
|||
|
|
color: #eab700;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Tomorrow Green */
|
|||
|
|
.hljs-string,
|
|||
|
|
.hljs-symbol,
|
|||
|
|
.hljs-bullet,
|
|||
|
|
.hljs-addition {
|
|||
|
|
color: #718c00;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Tomorrow Blue */
|
|||
|
|
.hljs-title,
|
|||
|
|
.hljs-section {
|
|||
|
|
color: #4271ae;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Tomorrow Purple */
|
|||
|
|
.hljs-keyword,
|
|||
|
|
.hljs-selector-tag {
|
|||
|
|
color: #8959a8;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.hljs {
|
|||
|
|
display: block;
|
|||
|
|
overflow-x: auto;
|
|||
|
|
color: #4d4d4c;
|
|||
|
|
padding: 0.5em;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.hljs-emphasis {
|
|||
|
|
font-style: italic;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.hljs-strong {
|
|||
|
|
font-weight: bold;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
|
|||
|
|
<style>
|
|||
|
|
/*
|
|||
|
|
* Markdown PDF CSS
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
body {
|
|||
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif, "Meiryo";
|
|||
|
|
padding: 0 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pre {
|
|||
|
|
background-color: #f8f8f8;
|
|||
|
|
border: 1px solid #cccccc;
|
|||
|
|
border-radius: 3px;
|
|||
|
|
overflow-x: auto;
|
|||
|
|
white-space: pre-wrap;
|
|||
|
|
overflow-wrap: break-word;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pre:not(.hljs) {
|
|||
|
|
padding: 23px;
|
|||
|
|
line-height: 19px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
blockquote {
|
|||
|
|
background: rgba(127, 127, 127, 0.1);
|
|||
|
|
border-color: rgba(0, 122, 204, 0.5);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.emoji {
|
|||
|
|
height: 1.4em;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
code {
|
|||
|
|
font-size: 14px;
|
|||
|
|
line-height: 19px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* for inline code */
|
|||
|
|
:not(pre):not(.hljs) > code {
|
|||
|
|
color: #C9AE75; /* Change the old color so it seems less like an error */
|
|||
|
|
font-size: inherit;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Page Break : use <div class="page"/> to insert page break
|
|||
|
|
-------------------------------------------------------- */
|
|||
|
|
.page {
|
|||
|
|
page-break-after: always;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
</style>
|
|||
|
|
|
|||
|
|
<script src="https://unpkg.com/mermaid/dist/mermaid.min.js"></script>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<script>
|
|||
|
|
mermaid.initialize({
|
|||
|
|
startOnLoad: true,
|
|||
|
|
theme: document.body.classList.contains('vscode-dark') || document.body.classList.contains('vscode-high-contrast')
|
|||
|
|
? 'dark'
|
|||
|
|
: 'default'
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
<ul>
|
|||
|
|
<li><a href="#1-reference">1. Reference</a>
|
|||
|
|
<ul>
|
|||
|
|
<li><a href="#11-nextjs-authentication-made-easy-with-microsoft-entra-id">1.1. Next.js Authentication Made Easy with Microsoft Entra ID</a></li>
|
|||
|
|
<li><a href="#12-nextjs-16-middleware-deprecated---authentication-in-proxy-or-data-access-layer">1.2. Next.js 16 Middleware DEPRECATED - Authentication In Proxy Or Data Access Layer?</a></li>
|
|||
|
|
<li><a href="#13-overview-of-user-consent-and-how-to-manage-it-in-microsoft-entra--microsoft">1.3. Overview of user consent and how to manage it in Microsoft Entra | Microsoft</a></li>
|
|||
|
|
<li><a href="#14-add-tailwind-css-to-an-existing-next-js-project">1.4. Add Tailwind CSS to an Existing Next js Project</a></li>
|
|||
|
|
<li><a href="#15-how-to-style-material-ui-component-with-tailwindcss-in-react-project">1.5. How to style Material UI component with Tailwindcss in React project</a></li>
|
|||
|
|
<li><a href="#16-how-to-use-prisma-7-in-nextjs">1.6. How to Use Prisma 7 in Next.js</a></li>
|
|||
|
|
</ul>
|
|||
|
|
</li>
|
|||
|
|
<li><a href="#2-project-structure">2. Project Structure</a></li>
|
|||
|
|
<li><a href="#3-app-registrations">3. App Registrations</a>
|
|||
|
|
<ul>
|
|||
|
|
<li><a href="#31-permissions-needed">3.1. Permissions Needed</a></li>
|
|||
|
|
</ul>
|
|||
|
|
</li>
|
|||
|
|
<li><a href="#4-steps">4. Steps</a>
|
|||
|
|
<ul>
|
|||
|
|
<li><a href="#41-create-nextjs-app">4.1. create nextjs app</a></li>
|
|||
|
|
<li><a href="#42-create-env-file">4.2. Create .env file</a></li>
|
|||
|
|
<li><a href="#43-configure-git">4.3. Configure Git</a></li>
|
|||
|
|
<li><a href="#44-test-nextjs">4.4. Test nextjs</a></li>
|
|||
|
|
<li><a href="#45-check-authentication">4.5. Check Authentication</a></li>
|
|||
|
|
<li><a href="#46-install-nextauthjs-v5">4.6. Install NextAuth.js v5</a></li>
|
|||
|
|
</ul>
|
|||
|
|
</li>
|
|||
|
|
<li><a href="#5-env-files">5. .env files</a>
|
|||
|
|
<ul>
|
|||
|
|
<li><a href="#51-envlocal">5.1. .env.local</a></li>
|
|||
|
|
<li><a href="#52-env">5.2. .env</a></li>
|
|||
|
|
</ul>
|
|||
|
|
</li>
|
|||
|
|
<li><a href="#6-prisma-install">6. Prisma Install</a>
|
|||
|
|
<ul>
|
|||
|
|
<li><a href="#61-the-cli-is-for-development-tasks-like-migrations">6.1. The CLI is for development tasks like migrations</a></li>
|
|||
|
|
<li><a href="#62-create-the-prisma-folder">6.2. Create the prisma folder</a></li>
|
|||
|
|
</ul>
|
|||
|
|
</li>
|
|||
|
|
<li><a href="#7-run-this-whenever-you-change-your-schemaprisma-file">7. Run this whenever you change your schema.prisma file</a>
|
|||
|
|
<ul>
|
|||
|
|
<li><a href="#71-pro-tip-update-your-packagejson">7.1. Pro-Tip: Update your package.json</a></li>
|
|||
|
|
</ul>
|
|||
|
|
</li>
|
|||
|
|
<li><a href="#8-where-we-are-up-to-812026">8. Where we are up to 8/1/2026</a></li>
|
|||
|
|
<li><a href="#9-testing-creating-and-uploading-folders-and-files">9. Testing Creating And Uploading Folders and Files</a></li>
|
|||
|
|
<li><a href="#10--webcalibre-development-notes-1712026">10. 📚 WebCalibre Development Notes 17/1/2026</a>
|
|||
|
|
<ul>
|
|||
|
|
<li><a href="#101--project-overview">10.1. 🚀 Project Overview</a></li>
|
|||
|
|
<li><a href="#102--critical-technical-learnings">10.2. 🛠 Critical Technical Learnings</a>
|
|||
|
|
<ul>
|
|||
|
|
<li><a href="#1021-mui-x-v8-datagrid-toolbar-fix">10.2.1. MUI X v8 DataGrid Toolbar Fix</a></li>
|
|||
|
|
<li><a href="#1022-global-hidden-search">10.2.2. Global "Hidden" Search</a></li>
|
|||
|
|
<li><a href="#1023-server-side-extraction">10.2.3. Server-Side Extraction</a></li>
|
|||
|
|
</ul>
|
|||
|
|
</li>
|
|||
|
|
<li><a href="#103--essential-commands">10.3. 📑 Essential Commands</a></li>
|
|||
|
|
<li><a href="#104--future-roadmap">10.4. 📅 Future Roadmap</a>
|
|||
|
|
<ul>
|
|||
|
|
<li><a href="#1041-phase-1-metadata-utility-in-progress">10.4.1. Phase 1: Metadata Utility (In Progress)</a></li>
|
|||
|
|
<li><a href="#1042-phase-2-ui-enhancements">10.4.2. Phase 2: UI Enhancements</a></li>
|
|||
|
|
</ul>
|
|||
|
|
</li>
|
|||
|
|
<li><a href="#105--reminders">10.5. 💡 Reminders</a></li>
|
|||
|
|
<li><a href="#"></a></li>
|
|||
|
|
<li><a href="#dump-of-metadata-for-an-image">Dump of metadata for an image</a></li>
|
|||
|
|
<li><a href="#where-the-code-should-exist">Where the code should exist</a>
|
|||
|
|
<ul>
|
|||
|
|
<li><a href="#1-where-to-put-the-fetch-call">1. Where to put the fetch call?</a></li>
|
|||
|
|
<li><a href="#2-where-to-put-getmetadatafromjson">2. Where to put <code>getMetadataFromJson</code>?</a></li>
|
|||
|
|
<li><a href="#%EF%B8%8F-how-it-looks-all-together">🏗️ How it looks all together</a></li>
|
|||
|
|
</ul>
|
|||
|
|
</li>
|
|||
|
|
</ul>
|
|||
|
|
</li>
|
|||
|
|
</ul>
|
|||
|
|
<h1 id="1-reference">1. Reference</h1>
|
|||
|
|
<h2 id="11-nextjs-authentication-made-easy-with-microsoft-entra-id">1.1. Next.js Authentication Made Easy with Microsoft Entra ID</h2>
|
|||
|
|
<p><a href="https://www.youtube.com/watch?v=tNLrMKZ-xmc&t=5s">Next.js Authentication Made Easy with Microsoft Entra ID</a></p>
|
|||
|
|
<h2 id="12-nextjs-16-middleware-deprecated---authentication-in-proxy-or-data-access-layer">1.2. Next.js 16 Middleware DEPRECATED - Authentication In Proxy Or Data Access Layer?</h2>
|
|||
|
|
<p><a href="https://www.youtube.com/watch?v=zNgCFXZLoRk">Next.js 16 Middleware DEPRECATED - Authentication In Proxy Or Data Access Layer?</a></p>
|
|||
|
|
<h2 id="13-overview-of-user-consent-and-how-to-manage-it-in-microsoft-entra--microsoft">1.3. Overview of user consent and how to manage it in Microsoft Entra | Microsoft</h2>
|
|||
|
|
<p><a href="https://www.youtube.com/watch?v=u6PyQkNi7xk">Overview of user consent and how to manage it in Microsoft Entra | Microsoft</a></p>
|
|||
|
|
<h2 id="14-add-tailwind-css-to-an-existing-next-js-project">1.4. Add Tailwind CSS to an Existing Next js Project</h2>
|
|||
|
|
<p><a href="https://www.youtube.com/watch?v=vRqTNuX46PY">Add Tailwind CSS to an Existing Next js Project</a></p>
|
|||
|
|
<h2 id="15-how-to-style-material-ui-component-with-tailwindcss-in-react-project">1.5. How to style Material UI component with Tailwindcss in React project</h2>
|
|||
|
|
<p><a href="https://www.youtube.com/watch?v=QQIfuMlA6TI">How to style Material UI component with Tailwindcss in React project</a></p>
|
|||
|
|
<h2 id="16-how-to-use-prisma-7-in-nextjs">1.6. How to Use Prisma 7 in Next.js</h2>
|
|||
|
|
<p><a href="https://www.youtube.com/watch?v=Ndhx_rNkoUw">How to Use Prisma 7 in Next.js</a></p>
|
|||
|
|
<h1 id="2-project-structure">2. Project Structure</h1>
|
|||
|
|
<p>The following is the desire structure</p>
|
|||
|
|
<pre class="hljs"><code><div>web-calibre/
|
|||
|
|
├── src/
|
|||
|
|
│ ├── app/ # All Routes (Pages & APIs)
|
|||
|
|
│ │ ├── api/ # API ROUTES
|
|||
|
|
│ │ │ ├── auth/ # NextAuth routes [...nextauth]
|
|||
|
|
│ │ │ └── upload/ # Custom API for OneDrive sessions
|
|||
|
|
│ │ ├── dashboard/ # Protected user area
|
|||
|
|
│ │ ├── layout.tsx # Global Layout
|
|||
|
|
│ │ └── page.tsx # Landing Page
|
|||
|
|
│ ├── components/ # MUI Components (FileTable, Navbar)
|
|||
|
|
│ ├── lib/ # Config (Prisma, Theme, Auth)
|
|||
|
|
│ │ ├── prisma.ts
|
|||
|
|
│ │ └── theme.ts <-- MUI Theme Defined Here
|
|||
|
|
│ └── middleware.ts <-- Protected Routes Logic
|
|||
|
|
├── prisma/ # DB Schema
|
|||
|
|
└── .env # Secrets
|
|||
|
|
</div></code></pre>
|
|||
|
|
<h1 id="3-app-registrations">3. App Registrations</h1>
|
|||
|
|
<p>WebCalibre2
|
|||
|
|
Application (client) ID = 'b549931a-f491-436b-b7bc-d37d8ca3c17e'</p>
|
|||
|
|
<p>Object ID ='0b8256a2-e2ac-472b-896a-4b5845bc32fe'</p>
|
|||
|
|
<p>Directory (tenant) ID = '1c06ce7c-7884-4796-8652-d4c32d75a5d0'</p>
|
|||
|
|
<p>Expires = 1/3/2028</p>
|
|||
|
|
<p>Value='O3p8Q~oMph-0kSLwkvzEJzJdx_iHGOjJjrr5Pa1A'</p>
|
|||
|
|
<p>Secret ID='6a242be1-c711-4cc3-a132-03c1f57993ed'</p>
|
|||
|
|
<p><img src="file:///Users/stephenlohning/Documents/01_Projects/124_webcalibre2/docs/images/Certicates_secrets.png" alt="WebCalibre2 | Certificates & secrets"></p>
|
|||
|
|
<p>Since you are using NextAuth.js, the URL must follow a very specific pattern.</p>
|
|||
|
|
<p>For local development: http://localhost:3000/api/auth/callback/azure-ad</p>
|
|||
|
|
<p>For production: https://your-domain.com/api/auth/callback/azure-ad</p>
|
|||
|
|
<p>Note: Replace azure-ad with whatever ID you give the provider in your code. By default, in NextAuth, it is usually azure-ad.</p>
|
|||
|
|
<p>WebCalibre3</p>
|
|||
|
|
<table>
|
|||
|
|
<thead>
|
|||
|
|
<tr>
|
|||
|
|
<th>Display name</th>
|
|||
|
|
<th>WebCalibre3</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
<tr>
|
|||
|
|
<td>Application (client) ID</td>
|
|||
|
|
<td>a95ef644-cb9d-465c-8dae-f822a78a8ac3</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>Object ID</td>
|
|||
|
|
<td>1e1f2bda-661c-4ad0-9aae-82313c603076</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>Directory (tenant) ID.</td>
|
|||
|
|
<td>1c06ce7c-7884-4796-8652-d4c32d75a5d0</td>
|
|||
|
|
</tr>
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
<p>Client credentials</p>
|
|||
|
|
<table>
|
|||
|
|
<thead>
|
|||
|
|
<tr>
|
|||
|
|
<th>Description</th>
|
|||
|
|
<th>Expires</th>
|
|||
|
|
<th>Value</th>
|
|||
|
|
<th>Secret ID</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
<tr>
|
|||
|
|
<td>WebCalibre3</td>
|
|||
|
|
<td>1/5/2028</td>
|
|||
|
|
<td>VZ48Q~EOsgZcgBI25cc0zys.h9zM7hN9I7DhZdnW</td>
|
|||
|
|
<td>d647cf28-80ed-45fb-a89e-e2bc323a40e9</td>
|
|||
|
|
</tr>
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
<h2 id="31-permissions-needed">3.1. Permissions Needed</h2>
|
|||
|
|
<p><img src="file:///Users/stephenlohning/Documents/01_Projects/124_webcalibre2/docs/images/PermissionsNeeded.png" alt="Permissions Needed"></p>
|
|||
|
|
<h1 id="4-steps">4. Steps</h1>
|
|||
|
|
<h2 id="41-create-nextjs-app">4.1. create nextjs app</h2>
|
|||
|
|
<p>The following command creates the nextjs folder structure</p>
|
|||
|
|
<p>Yes, Next.js has a specific syntax to create a project in your current directory instead of creating a new subfolder.</p>
|
|||
|
|
<p>To do this, navigate to your empty project folder in the terminal and run:</p>
|
|||
|
|
<pre class="hljs"><code><div>
|
|||
|
|
npx create-next-app@latest .
|
|||
|
|
</div></code></pre>
|
|||
|
|
<p><strong>Important Details:</strong></p>
|
|||
|
|
<ol>
|
|||
|
|
<li>
|
|||
|
|
<p>The Dot (.): This tells the installer to use the current directory as the project root.</p>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p>Naming Conflict: If the folder name contains capital letters (e.g., WebCalibre), the command will fail with an error. Next.js requires the root folder name to be all lowercase and URL-friendly. You must rename your folder to web-calibre before running the command.
|
|||
|
|
This caused me rename to all lower case.</p>
|
|||
|
|
</li>
|
|||
|
|
</ol>
|
|||
|
|
<p>3.Empty Folder: The directory must be empty (except for hidden files like .git). If you already have files in there, the installer will stop to prevent overwriting your work.</p>
|
|||
|
|
<p>What happens next?
|
|||
|
|
Even though you are using the current directory, the interactive prompt will still ask you:</p>
|
|||
|
|
<p>"What is your project named?" You can just hit Enter to accept the current folder name.</p>
|
|||
|
|
<p>Configuration: It will then ask you about TypeScript, ESLint, Tailwind CSS, the src/ directory, and the App Router.</p>
|
|||
|
|
<p>Based on our previous discussions, I recommend selecting "Yes" for TypeScript, ESLint, Tailwind, the src/ directory, and the App Router to match the professional structure we planned for WebCalibre.</p>
|
|||
|
|
<h2 id="42-create-env-file">4.2. Create .env file</h2>
|
|||
|
|
<p>In Next.js, the distinction between .env and .env.local is primarily about security and <strong>overriding defaults</strong>.
|
|||
|
|
While both store key-value pairs, they are handled differently by Git and the Next.js runtime.</p>
|
|||
|
|
<ol>
|
|||
|
|
<li>Security & Version Control (Git)The most important reason is that .env.local is automatically ignored by Git in standard Next.js projects.</li>
|
|||
|
|
</ol>
|
|||
|
|
<ul>
|
|||
|
|
<li>.env: Intended for "safe" default values that you want to share with your entire team. It is committed to your repository so that every developer has the same baseline configuration.</li>
|
|||
|
|
<li>.env.local: Intended for secrets (API keys, database passwords, Azure secrets). Because it is not committed to Git, your private credentials never end up on GitHub or in your production code history.</li>
|
|||
|
|
</ul>
|
|||
|
|
<ol start="2">
|
|||
|
|
<li>Priority & Overriding
|
|||
|
|
Next.js uses a "hierarchy" of loading.1 If you define the same variable in both files, .env.local wins.
|
|||
|
|
This allows you to set a generic placeholder in .env for the team, but use your specific local developer settings on your own machine.
|
|||
|
|
For example:</li>
|
|||
|
|
</ol>
|
|||
|
|
<ul>
|
|||
|
|
<li>In .env: DATABASE_URL=postgres://localhost:5432/mydb (The team's default)</li>
|
|||
|
|
<li>In .env.local:
|
|||
|
|
DATABASE_URL=postgres://admin:password123@localhost:5432/my_private_dev_db (Your personal setup)</li>
|
|||
|
|
</ul>
|
|||
|
|
<p><strong>The Next.js Loading Order</strong>
|
|||
|
|
Next.js looks for variables in this specific order (from highest priority to lowest):</p>
|
|||
|
|
<ol>
|
|||
|
|
<li>process.env (System environment variables)</li>
|
|||
|
|
<li>.env.development.local (Only during npm run dev)</li>
|
|||
|
|
<li>.env.local (Available in all environments except test)</li>
|
|||
|
|
<li>.env.development</li>
|
|||
|
|
<li>.env (The final fallback)</li>
|
|||
|
|
</ol>
|
|||
|
|
<pre class="hljs"><code><div>DATABASE_URL="postgresql://stephen:Web2025$$@192.168.1.210:5432/webcalibre2"
|
|||
|
|
|
|||
|
|
# Credentials for your Microsoft App (NextAuth)
|
|||
|
|
AZURE_AD_CLIENT_ID="549931a-f491-436b-b7bc-d37d8ca3c17e"
|
|||
|
|
AZURE_AD_CLIENT_SECRET="6a242be1-c711-4cc3-a132-03c1f57993ed"
|
|||
|
|
AZURE_AD_TENANT_ID="1c06ce7c-7884-4796-8652-d4c32d75a5d0"
|
|||
|
|
</div></code></pre>
|
|||
|
|
<p><strong>Summary Comparison Table</strong></p>
|
|||
|
|
<table>
|
|||
|
|
<thead>
|
|||
|
|
<tr>
|
|||
|
|
<th>Feature</th>
|
|||
|
|
<th>.env</th>
|
|||
|
|
<th>.env.local</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
<tr>
|
|||
|
|
<td>Purpose</td>
|
|||
|
|
<td>Shared defaults/constants</td>
|
|||
|
|
<td>Private secrets & local overrides</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>Committed to Git?</td>
|
|||
|
|
<td>Yes</td>
|
|||
|
|
<td>No (Added to .gitignore)</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>Sensitive Data?</td>
|
|||
|
|
<td>NEVER</td>
|
|||
|
|
<td>YES (API Keys, Secrets)</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td>Scope</td>
|
|||
|
|
<td>All developers/environments</td>
|
|||
|
|
<td>Just your machine</td>
|
|||
|
|
</tr>
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
<p>**Best Practice Tip: ** Since .env.local isn't shared on GitHub, it’s a good idea to create a file named .env.example in your project. This file should contain the names of the keys (e.g., AZURE_AD_CLIENT_SECRET=) but leave the values blank, so other developers know which variables they need to create on their own machines.</p>
|
|||
|
|
<h2 id="43-configure-git">4.3. Configure Git</h2>
|
|||
|
|
<p>Nextjs initializes git by default. But is doesn't add some configs that I do</p>
|
|||
|
|
<p>git config --global push.followTags true</p>
|
|||
|
|
<p>git remote add origin "/Users/stephenlohning/Library/CloudStorage/OneDrive-Personal/Documents/10_GIT_Repositories/124_WebCalibre2.git"</p>
|
|||
|
|
<p>git remote -v
|
|||
|
|
origin /Users/stephenlohning/Library/CloudStorage/OneDrive-Personal/Documents/10_GIT_Repositories/124_WebCalibre2.git (fetch)
|
|||
|
|
origin /Users/stephenlohning/Library/CloudStorage/OneDrive-Personal/Documents/10_GIT_Repositories/124_WebCalibre2.git (push)</p>
|
|||
|
|
<h2 id="44-test-nextjs">4.4. Test nextjs</h2>
|
|||
|
|
<pre class="hljs"><code><div>npm run dev
|
|||
|
|
|
|||
|
|
> 124_webcalibre2@0.1.0 dev
|
|||
|
|
> next dev
|
|||
|
|
|
|||
|
|
▲ Next.js 16.1.1 (Turbopack)
|
|||
|
|
- Local: http://localhost:3000
|
|||
|
|
- Network: http://192.168.1.100:3000
|
|||
|
|
- Environments: .env
|
|||
|
|
|
|||
|
|
✓ Starting...
|
|||
|
|
✓ Ready <span class="hljs-keyword">in</span> 648ms
|
|||
|
|
</div></code></pre>
|
|||
|
|
<p>If it working proceed</p>
|
|||
|
|
<h2 id="45-check-authentication">4.5. Check Authentication</h2>
|
|||
|
|
<p>I ran through the Video:-</p>
|
|||
|
|
<p><a href="https://www.youtube.com/watch?v=tNLrMKZ-xmc&t=5s">Next.js Authentication Made Easy with Microsoft Entra ID</a></p>
|
|||
|
|
<h2 id="46-install-nextauthjs-v5">4.6. Install NextAuth.js v5</h2>
|
|||
|
|
<p>npm install next-auth@beta</p>
|
|||
|
|
<p>Setup Environment
|
|||
|
|
The only environment variable that is mandatory is the AUTH_SECRET. This is a random value used by the library to encrypt tokens and email verification hashes. (See Deployment to learn more). You can generate one via the official Auth.js CLI running:</p>
|
|||
|
|
<p>npx auth secret</p>
|
|||
|
|
<p>created .env.local</p>
|
|||
|
|
<pre class="hljs"><code><div>AUTH_SECRET="7cz3Z4kUI2kB3mdAPo58iioUDLSRJ92X8+boEixvO8k=" # Added by `npx auth`. Read more: https://cli.authjs.dev
|
|||
|
|
</div></code></pre>
|
|||
|
|
<h1 id="5-env-files">5. .env files</h1>
|
|||
|
|
<h2 id="51-envlocal">5.1. .env.local</h2>
|
|||
|
|
<pre class="hljs"><code><div># values generated by Gemini
|
|||
|
|
# Generated for security
|
|||
|
|
AUTH_SECRET="7cz3Z4kUI2kB3mdAPo58iioUDLSRJ92X8+boEixvO8k="
|
|||
|
|
|
|||
|
|
# Use 'common' for multi-tenant (Work + Personal) support
|
|||
|
|
AUTH_MICROSOFT_ENTRA_ID_TENANT_ID="common"
|
|||
|
|
|
|||
|
|
# The Application (client) ID
|
|||
|
|
AUTH_MICROSOFT_ENTRA_ID_ID="b549931a-f491-436b-b7bc-d37d8ca3c17e"
|
|||
|
|
|
|||
|
|
# The Client Secret VALUE (ensure no leading '6' or spaces)
|
|||
|
|
AUTH_MICROSOFT_ENTRA_ID_SECRET="O3p8Q~oMph-0kSLwkvzEJzJdx_iHGOjJjrr5Pa1A"
|
|||
|
|
|
|||
|
|
# Required to tell Auth.js to trust your localhost/proxy URL
|
|||
|
|
AUTH_TRUST_HOST=true
|
|||
|
|
</div></code></pre>
|
|||
|
|
<h2 id="52-env">5.2. .env</h2>
|
|||
|
|
<pre class="hljs"><code><div>DATABASE_URL="postgresql://stephen:Web2025$$@192.168.1.210:5432/webcalibre2"
|
|||
|
|
|
|||
|
|
# Credentials for your Microsoft App (NextAuth)
|
|||
|
|
AZURE_AD_CLIENT_ID="549931a-f491-436b-b7bc-d37d8ca3c17e"
|
|||
|
|
AZURE_AD_CLIENT_SECRET="6a242be1-c711-4cc3-a132-03c1f57993ed"
|
|||
|
|
AZURE_AD_TENANT_ID="1c06ce7c-7884-4796-8652-d4c32d75a5d0"
|
|||
|
|
</div></code></pre>
|
|||
|
|
<h1 id="6-prisma-install">6. Prisma Install</h1>
|
|||
|
|
<p>To get Prisma fully installed and ready to talk to your PostgreSQL database, you need to install the CLI (for migrations) and the Client (for your code to query the database).</p>
|
|||
|
|
<p>Run these three commands in your terminal:</p>
|
|||
|
|
<ol>
|
|||
|
|
<li>Install the Prisma packages
|
|||
|
|
Bash</li>
|
|||
|
|
</ol>
|
|||
|
|
<h2 id="61-the-cli-is-for-development-tasks-like-migrations">6.1. The CLI is for development tasks like migrations</h2>
|
|||
|
|
<p>npm install prisma --save-dev</p>
|
|||
|
|
<p>##The Client is used by your Next.js code to talk to the DB</p>
|
|||
|
|
<pre class="hljs"><code><div>npm install @prisma/client
|
|||
|
|
</div></code></pre>
|
|||
|
|
<ol start="2">
|
|||
|
|
<li>Install the Dotenv helper
|
|||
|
|
Since you are keeping your DATABASE_URL in .env.local (and your password has special characters), you need this to bridge the gap between Next.js and the Prisma CLI:</li>
|
|||
|
|
</ol>
|
|||
|
|
<pre class="hljs"><code><div>
|
|||
|
|
npm install dotenv-cli --save-dev
|
|||
|
|
</div></code></pre>
|
|||
|
|
<ol start="3">
|
|||
|
|
<li>Initialize and Generate
|
|||
|
|
Once the packages are installed, you need to initialize the Prisma folder (if you haven't already) and generate the types for your FileNode model:</li>
|
|||
|
|
</ol>
|
|||
|
|
<p>Bash</p>
|
|||
|
|
<h2 id="62-create-the-prisma-folder">6.2. Create the prisma folder</h2>
|
|||
|
|
<pre class="hljs"><code><div>npx prisma init
|
|||
|
|
</div></code></pre>
|
|||
|
|
<h1 id="7-run-this-whenever-you-change-your-schemaprisma-file">7. Run this whenever you change your schema.prisma file</h1>
|
|||
|
|
<p>npx dotenv -e .env.local -- npx prisma generate
|
|||
|
|
4. Apply your Schema to PostgreSQL
|
|||
|
|
Finally, run this to actually create the tables in your webcalibre2 database:</p>
|
|||
|
|
<p>npx dotenv -e .env.local -- npx prisma migrate dev --name init_database</p>
|
|||
|
|
<h2 id="71-pro-tip-update-your-packagejson">7.1. Pro-Tip: Update your package.json</h2>
|
|||
|
|
<p>To avoid typing that long dotenv command every time, open your package.json and add this to the "scripts" section:</p>
|
|||
|
|
<p>JSON</p>
|
|||
|
|
<p>"scripts": {
|
|||
|
|
"db:migrate": "dotenv -e .env.local -- prisma migrate dev",
|
|||
|
|
"db:studio": "dotenv -e .env.local -- prisma studio"
|
|||
|
|
}
|
|||
|
|
Now, you can just run npm run db:migrate whenever you update your schema!</p>
|
|||
|
|
<h1 id="8-where-we-are-up-to-812026">8. Where we are up to 8/1/2026</h1>
|
|||
|
|
<p>We can authenticate with MS Azure, upload a file and store it on my personal OneDrive.
|
|||
|
|
I can see a few problems,</p>
|
|||
|
|
<ol>
|
|||
|
|
<li>
|
|||
|
|
<p>if the file gets stored with its original filename, if I store the same file again it tells that it fail, but in reality it did not fail the it got renamed with 1.pdf added this is not added data base.</p>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p>We may be better to create a folder and put the file in the folder, to me the logical name would id of the file, but that is automatically create and assigned when the data is stored as a UUID.</p>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p>We would have to create the UUID ourselves, so that we can create a folder with that specific UUID and store the file in the folder.</p>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p>The Upload file Page needs to be able to create folder as well select a parent , add a description of either the file</p>
|
|||
|
|
</li>
|
|||
|
|
</ol>
|
|||
|
|
<h1 id="9-testing-creating-and-uploading-folders-and-files">9. Testing Creating And Uploading Folders and Files</h1>
|
|||
|
|
<p>The functionality works may not the best user interface</p>
|
|||
|
|
<p>Projects ID 0371fdfe-a2d1-4f25-b229-804f299bc064
|
|||
|
|
Project-1 ID c8eebe0e-6e90-4cfb-8e44-c05f7064f3b1 parentID 0371fdfe-a2d1-4f25-b229-804f299bc064
|
|||
|
|
AS-NZS3000-2018.pdf parentID c8eebe0e-6e90-4cfb-8e44-c05f7064f3b1</p>
|
|||
|
|
<p>Now we will go now and add metadata</p>
|
|||
|
|
<p>Now we have the metadata along with some great filters on the data board page</p>
|
|||
|
|
<p>Now we are going to implement an an update of a file or folder stored. 12/1/2026</p>
|
|||
|
|
<p>For download there 2 function we need to implement</p>
|
|||
|
|
<ol>
|
|||
|
|
<li>
|
|||
|
|
<p>down load the file to ~/user/Downloads, this should work for any file type including pdf</p>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p>if the file is a pdf open the file in another tab in the browser as most browser support reading a pdf</p>
|
|||
|
|
</li>
|
|||
|
|
</ol>
|
|||
|
|
<p>For download there 2 function we need to implement</p>
|
|||
|
|
<ol>
|
|||
|
|
<li>
|
|||
|
|
<p>down load the file to ~/user/Downloads, this should work for any file type including pdf</p>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p>if the file is a pdf open the file in another tab in the browser as most browser support reading a pdf</p>
|
|||
|
|
</li>
|
|||
|
|
</ol>
|
|||
|
|
<p>So we need to implement 2 new actions, downLoad file and open pdf to read. Does this make sense ?</p>
|
|||
|
|
<h1 id="10-%F0%9F%93%9A-webcalibre-development-notes-1712026">10. 📚 WebCalibre Development Notes 17/1/2026</h1>
|
|||
|
|
<h2 id="101-%F0%9F%9A%80-project-overview">10.1. 🚀 Project Overview</h2>
|
|||
|
|
<p>A Next.js (App Router) dashboard for managing a OneDrive-synced file library.
|
|||
|
|
Uses <strong>Prisma + PostgreSQL</strong> for metadata storage and <strong>MUI X v8 (DataGrid)</strong> for the frontend.</p>
|
|||
|
|
<hr>
|
|||
|
|
<h2 id="102-%F0%9F%9B%A0-critical-technical-learnings">10.2. 🛠 Critical Technical Learnings</h2>
|
|||
|
|
<h3 id="1021-mui-x-v8-datagrid-toolbar-fix">10.2.1. MUI X v8 DataGrid Toolbar Fix</h3>
|
|||
|
|
<ul>
|
|||
|
|
<li><strong>Problem:</strong> The Search Bar and "Library" title often disappear even when defined in <code>slots</code>.</li>
|
|||
|
|
<li><strong>Solution:</strong> - You MUST include the <code>showToolbar</code> prop on the <code><DataGrid /></code>.
|
|||
|
|
<ul>
|
|||
|
|
<li>Use the <strong>Persistent Quick Filter</strong> model: set the <code>expanded</code> prop on the <code><QuickFilter /></code> component within the custom toolbar to ensure it doesn't collapse.</li>
|
|||
|
|
<li>Use <code>slotProps.textField</code> (not <code>input</code>) to customize the search input in MUI v7/v8.</li>
|
|||
|
|
</ul>
|
|||
|
|
</li>
|
|||
|
|
</ul>
|
|||
|
|
<h3 id="1022-global-%22hidden%22-search">10.2.2. Global "Hidden" Search</h3>
|
|||
|
|
<ul>
|
|||
|
|
<li>To allow the search bar to find "Author", "ISBN", or "Dimensions" without cluttering the UI:
|
|||
|
|
<ul>
|
|||
|
|
<li>Create a column (e.g., <code>metadata_search</code>) with <code>width: 0</code>.</li>
|
|||
|
|
<li>Use a <code>valueGetter</code> to return <code>JSON.stringify(row.metadata)</code>.</li>
|
|||
|
|
<li>Hide the column in <code>initialState.columns.columnVisibilityModel</code>.</li>
|
|||
|
|
<li>The DataGrid filtering engine will now index this hidden string.</li>
|
|||
|
|
</ul>
|
|||
|
|
</li>
|
|||
|
|
</ul>
|
|||
|
|
<h3 id="1023-server-side-extraction">10.2.3. Server-Side Extraction</h3>
|
|||
|
|
<ul>
|
|||
|
|
<li>Metadata extraction from file binaries (PDF, EPUB, Images) <strong>must</strong> occur on the server.</li>
|
|||
|
|
<li>Libraries like <code>sharp</code> and <code>pdf-parse</code> are Node.js-based and will crash if imported into client components.</li>
|
|||
|
|
</ul>
|
|||
|
|
<hr>
|
|||
|
|
<h2 id="103-%F0%9F%93%91-essential-commands">10.3. 📑 Essential Commands</h2>
|
|||
|
|
<table>
|
|||
|
|
<thead>
|
|||
|
|
<tr>
|
|||
|
|
<th style="text-align:left">Command</th>
|
|||
|
|
<th style="text-align:left">Purpose</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
<tr>
|
|||
|
|
<td style="text-align:left"><code>npx tsc --noEmit</code></td>
|
|||
|
|
<td style="text-align:left"><strong>Run this frequently.</strong> Validates types across the entire project. Finds errors your IDE might miss.</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td style="text-align:left"><code>npx prisma generate</code></td>
|
|||
|
|
<td style="text-align:left">Updates the Prisma Client types after schema changes.</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td style="text-align:left"><code>npx prisma db push</code></td>
|
|||
|
|
<td style="text-align:left">Pushes schema changes to the PostgreSQL database.</td>
|
|||
|
|
</tr>
|
|||
|
|
<tr>
|
|||
|
|
<td style="text-align:left"><code>npm run dev</code></td>
|
|||
|
|
<td style="text-align:left">Starts the development server.</td>
|
|||
|
|
</tr>
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
<hr>
|
|||
|
|
<h2 id="104-%F0%9F%93%85-future-roadmap">10.4. 📅 Future Roadmap</h2>
|
|||
|
|
<h3 id="1041-phase-1-metadata-utility-in-progress">10.4.1. Phase 1: Metadata Utility (In Progress)</h3>
|
|||
|
|
<ul>
|
|||
|
|
<li><input type="checkbox" id="checkbox0"><label for="checkbox0">Implement </label><code>src/lib/metadata-extractor.ts</code>.</li>
|
|||
|
|
<li><input type="checkbox" id="checkbox1"><label for="checkbox1">Integrate extraction into the </label><code>syncOneDrive</code> Server Action.</li>
|
|||
|
|
<li><input type="checkbox" id="checkbox2"><label for="checkbox2">Supported formats: PDF (</label><code>pdf-parse</code>), EPUB (<code>node-epub-utils</code>), Images (<code>sharp</code>).</li>
|
|||
|
|
</ul>
|
|||
|
|
<h3 id="1042-phase-2-ui-enhancements">10.4.2. Phase 2: UI Enhancements</h3>
|
|||
|
|
<ul>
|
|||
|
|
<li><input type="checkbox" id="checkbox3"><label for="checkbox3">Add "Last Synced" timestamp state to the dashboard header.</label></li>
|
|||
|
|
<li><input type="checkbox" id="checkbox4"><label for="checkbox4">Add dedicated sortable columns for "Author" and "File Type".</label></li>
|
|||
|
|
<li><input type="checkbox" id="checkbox5"><label for="checkbox5">Implement folder-specific row styling.</label></li>
|
|||
|
|
</ul>
|
|||
|
|
<hr>
|
|||
|
|
<h2 id="105-%F0%9F%92%A1-reminders">10.5. 💡 Reminders</h2>
|
|||
|
|
<ul>
|
|||
|
|
<li><strong>Hard Refresh:</strong> If the DataGrid UI behaves weirdly after code changes, use <code>Cmd + Shift + R</code> or <code>Ctrl + F5</code>.</li>
|
|||
|
|
<li><strong>Z-Index:</strong> The search bar animation uses <code>gridArea: '1 / 1'</code>. If icons overlap, check the styled-components logic in <code>dashboard-view.tsx</code>.</li>
|
|||
|
|
</ul>
|
|||
|
|
<p>1.AUIAfM4GHIR4lkeGUtTDLXWl0BqTSbWR9GtDt7zTfYyjwX6kAElCAA.BQABAwIAAAADAOz_BQD0_yyFlWKgjcwr_ed5f58Tdi53aj4fqbWRmjTI7vE5M94UQZT5SZLBxnTN7Psa7w-KJCMfY3AAzGgJzfOXOHGXSySvCzC6IlHwXL7uXLZYf84jdhgNEP2F1E1nz9ecmsG4tfjvMzwNPsh9nlY48Cz_qh_KDLu2odNesttqW7Om3MxsXLakskvMXynX07lJT6gbXT9OMoFk_F3M4Dz9DoYJD6CACZPdKaYqMeesfPt-B6Yj7tq1q9L4RUc6Fbrsew9chMqg1vurMlggcX1aBU7LF6JP50B3IkOB4qXrGDJtxM61gF64EXRay5UYVtWMh09et7SX4FcLZlHTyxpsvjSxaKxfpY9DFY2y0dhp0Ce5dZXoK7pplQwcxsv-dCeZmk4A3dqvAdxM6p0XNrowlOawpAlWqHoIC1ZmESDpyxueP0rlpfD9h3wkWcVAG9xOS_d-W9Z3y1G9PgBycOysvQTufueLs4PCjjId9-5AI4UaecQyUtpRCJnOxp23_e5ROLduihcDMrV87D8_ZN6nl49jFqr7kQZHbLanuVht18vSUyk31c9OtkFmC4p48_1xoIe5sYlPjoUd9Misgef7EziwdzapRyr649kb4Em_XMmJf_Y5zVDpT6NXRqlQAw3AhP53Px12hQsGsFkd0kzDBuwUSrKlda5E2lom4Wg5uXwb4zLD5DA6xwKfxBrAYICZT8QQYG5fTHjSP_2t9gs_K9-1-7UqKTb2VWGYf25QVnhgBXL1KbzosmFBOKjr31XjYo8IbgMjuEDGP-YfmyYOvmRg2Lx-VNlu8gTPcdSsdIivFSvEvHdUmIeReHMIdKjr0ZgVYwTtzG7QBs-8UXRvNo6IWecZnHEjx0QSLztyNXicgeOmT4gKDeJ0YT6rkWkNg_B8fYpx3j11ceRKpgQzm2E7UalGgBsbAHA1a4jnM7NrDv4TY_A_LRTabHPVrHpQWZ2HOJi5UlTQLv8xbiiJf94HVlxwncJd6YPy7ARjhvIA5nE5IiZJ8ZSUFwg4waCFibV-NDqiglo7YBCfx3DOrLsNjuufYQnjog_IDi6ac-DG7ulKS01nmAq7WfvshiW_mXLmwtPVusAcmLcfD-40B3kh12Cv-_EXSzfaVPP7M1Obi13TyoQDeVl-pMJuEC68xHzqA78SmZR8WluDRAJ5kOLBggvPbuHRFpkqtvFG8sGNzOqiBkq30qUErfk2Ao-SnHAh3fa3CAM2aYf3LdzXHh2_qGoVnrKdMfnUI14H6vrTxxBpDUinYejr8rLzbjH6tjDYWvjen5-BrDzOjazI7_ctS8zdLugG093kbWlYDW26FgYnKtdPCi-7F-emm8HGD82V_5e7Jc1qVs1G_D5eu0ohb_B1ky_AmxgCE8oL9Bdx1ehHnijNCarubf_MWMcloAv0XNxvrU4PoXdb7PgVBpYsKRT7tdnOGpokQj2n2xQSHvQVypd8xkHYloczdlmi9zvytM_Da8jnLdE0Td3mlnI-jlovrbzykD4YhS_ZcwIc_8c_WiHDeC3yAeTTx0EhuNh89HFSsmfod2BBaRgvf5SRpNwUjxd0PROvrGD_Hl0emtsC9YWdfULQBk3SKqJMV0x7Zv_Erj8JZoMzXbe8jv5bM4AmYqOPqznkXtAWAkedqLVYug0od9AgMIn-FFPpH4AvmHhhliMevIw74yEXCj28dxlHBZvyyqpbEwp8vr_kTjarjyp3s1p6QsYEoQ82vx-nQPiVFwWeGDsJcSmUD4FbtVIVCGY3p2cCtZHyRRWAK2LGgO32v3DhK7FSLKV-3f0LAafqdpZyGnqoF7pwn_RcpZz8l9tGctFl8Xd50Y1naLz8F08SlbJ197xdjzHLQ8r4jsVn5m9BkGbR0Kv3olXGRmtr5dkqGI-68_FCjkkqpwfOHg</p>
|
|||
|
|
<p>1.AUIAfM4GHIR4lkeGUtTDLXWl0BqTSbWR9GtDt7zTfYyjwX6kAElCAA.BQABAwIAAAADAOz_BQD0_yyFlWKgjcwr_ed5f58Tdi53aj4fqbWRmjTI7vE5M94UQZT5SZLBxnTN7Psa7w-KJCMfY3AAzGgJzfOXOHGXSySvCzC6IlHwXL7uXLZYf84jdhgNEP2F1E1nz9ecmsG4tfjvMzwNPsh9nlY48Cz_qh_KDLu2odNesttqW7Om3MxsXLakskvMXynX07lJT6gbXT9OMoFk_F3M4Dz9DoYJD6CACZPdKaYqMeesfPt-B6Yj7tq1q9L4RUc6Fbrsew9chMqg1vurMlggcX1aBU7LF6JP50B3IkOB4qXrGDJtxM61gF64EXRay5UYVtWMh09et7SX4FcLZlHTyxpsvjSxaKxfpY9DFY2y0dhp0Ce5dZXoK7pplQwcxsv-dCeZmk4A3dqvAdxM6p0XNrowlOawpAlWqHoIC1ZmESDpyxueP0rlpfD9h3wkWcVAG9xOS_d-W9Z3y1G9PgBycOysvQTufueLs4PCjjId9-5AI4UaecQyUtpRCJnOxp23_e5ROLduihcDMrV87D8_ZN6nl49jFqr7kQZHbLanuVht18vSUyk31c9OtkFmC4p48_1xoIe5sYlPjoUd9Misgef7EziwdzapRyr649kb4Em_XMmJf_Y5zVDpT6NXRqlQAw3AhP53Px12hQsGsFkd0kzDBuwUSrKlda5E2lom4Wg5uXwb4zLD5DA6xwKfxBrAYICZT8QQYG5fTHjSP_2t9gs_K9-1-7UqKTb2VWGYf25QVnhgBXL1KbzosmFBOKjr31XjYo8IbgMjuEDGP-YfmyYOvmRg2Lx-VNlu8gTPcdSsdIivFSvEvHdUmIeReHMIdKjr0ZgVYwTtzG7QBs-8UXRvNo6IWecZnHEjx0QSLztyNXicgeOmT4gKDeJ0YT6rkWkNg_B8fYpx3j11ceRKpgQzm2E7UalGgBsbAHA1a4jnM7NrDv4TY_A_LRTabHPVrHpQWZ2HOJi5UlTQLv8xbiiJf94HVlxwncJd6YPy7ARjhvIA5nE5IiZJ8ZSUFwg4waCFibV-NDqiglo7YBCfx3DOrLsNjuufYQnjog_IDi6ac-DG7ulKS01nmAq7WfvshiW_mXLmwtPVusAcmLcfD-40B3kh12Cv-_EXSzfaVPP7M1Obi13TyoQDeVl-pMJuEC68xHzqA78SmZR8WluDRAJ5kOLBggvPbuHRFpkqtvFG8sGNzOqiBkq30qUErfk2Ao-SnHAh3fa3CAM2aYf3LdzXHh2_qGoVnrKdMfnUI14H6vrTxxBpDUinYejr8rLzbjH6tjDYWvjen5-BrDzOjazI7_ctS8zdLugG093kbWlYDW26FgYnKtdPCi-7F-emm8HGD82V_5e7Jc1qVs1G_D5eu0ohb_B1ky_AmxgCE8oL9Bdx1ehHnijNCarubf_MWMcloAv0XNxvrU4PoXdb7PgVBpYsKRT7tdnOGpokQj2n2xQSHvQVypd8xkHYloczdlmi9zvytM_Da8jnLdE0Td3mlnI-jlovrbzykD4YhS_ZcwIc_8c_WiHDeC3yAeTTx0EhuNh89HFSsmfod2BBaRgvf5SRpNwUjxd0PROvrGD_Hl0emtsC9YWdfULQBk3SKqJMV0x7Zv_Erj8JZoMzXbe8jv5bM4AmYqOPqznkXtAWAkedqLVYug0od9AgMIn-FFPpH4AvmHhhliMevIw74yEXCj28dxlHBZvyyqpbEwp8vr_kTjarjyp3s1p6QsYEoQ82vx-nQPiVFwWeGDsJcSmUD4FbtVIVCGY3p2cCtZHyRRWAK2LGgO32v3DhK7FSLKV-3f0LAafqdpZyGnqoF7pwn_RcpZz8l9tGctFl8Xd50Y1naLz8F08SlbJ197xdjzHLQ8r4jsVn5m9BkGbR0Kv3olXGRmtr5dkqGI-68_FCjkkqpwfOHg</p>
|
|||
|
|
<p>1.AUIAfM4GHIR4lkeGUtTDLXWl0BqTSbWR9GtDt7zTfYyjwX6kAElCAA.BQABAwIAAAADAOz_BQD0_yyFlWKgjcwr_ed5f58Tdi53aj4fqbWRmjTI7vE5M94UQZT5SZLBxnTN7Psa7w-KJCMfY3AAzGgJzfOXOHGXSySvCzC6IlHwXL7uXLZYf84jdhgNEP2F1E1nz9ecmsG4tfjvMzwNPsh9nlY48Cz_qh_KDLu2odNesttqW7Om3MxsXLakskvMXynX07lJT6gbXT9OMoFk_F3M4Dz9DoYJD6CACZPdKaYqMeesfPt-B6Yj7tq1q9L4RUc6Fbrsew9chMqg1vurMlggcX1aBU7LF6JP50B3IkOB4qXrGDJtxM61gF64EXRay5UYVtWMh09et7SX4FcLZlHTyxpsvjSxaKxfpY9DFY2y0dhp0Ce5dZXoK7pplQwcxsv-dCeZmk4A3dqvAdxM6p0XNrowlOawpAlWqHoIC1ZmESDpyxueP0rlpfD9h3wkWcVAG9xOS_d-W9Z3y1G9PgBycOysvQTufueLs4PCjjId9-5AI4UaecQyUtpRCJnOxp23_e5ROLduihcDMrV87D8_ZN6nl49jFqr7kQZHbLanuVht18vSUyk31c9OtkFmC4p48_1xoIe5sYlPjoUd9Misgef7EziwdzapRyr649kb4Em_XMmJf_Y5zVDpT6NXRqlQAw3AhP53Px12hQsGsFkd0kzDBuwUSrKlda5E2lom4Wg5uXwb4zLD5DA6xwKfxBrAYICZT8QQYG5fTHjSP_2t9gs_K9-1-7UqKTb2VWGYf25QVnhgBXL1KbzosmFBOKjr31XjYo8IbgMjuEDGP-YfmyYOvmRg2Lx-VNlu8gTPcdSsdIivFSvEvHdUmIeReHMIdKjr0ZgVYwTtzG7QBs-8UXRvNo6IWecZnHEjx0QSLztyNXicgeOmT4gKDeJ0YT6rkWkNg_B8fYpx3j11ceRKpgQzm2E7UalGgBsbAHA1a4jnM7NrDv4TY_A_LRTabHPVrHpQWZ2HOJi5UlTQLv8xbiiJf94HVlxwncJd6YPy7ARjhvIA5nE5IiZJ8ZSUFwg4waCFibV-NDqiglo7YBCfx3DOrLsNjuufYQnjog_IDi6ac-DG7ulKS01nmAq7WfvshiW_mXLmwtPVusAcmLcfD-40B3kh12Cv-_EXSzfaVPP7M1Obi13TyoQDeVl-pMJuEC68xHzqA78SmZR8WluDRAJ5kOLBggvPbuHRFpkqtvFG8sGNzOqiBkq30qUErfk2Ao-SnHAh3fa3CAM2aYf3LdzXHh2_qGoVnrKdMfnUI14H6vrTxxBpDUinYejr8rLzbjH6tjDYWvjen5-BrDzOjazI7_ctS8zdLugG093kbWlYDW26FgYnKtdPCi-7F-emm8HGD82V_5e7Jc1qVs1G_D5eu0ohb_B1ky_AmxgCE8oL9Bdx1ehHnijNCarubf_MWMcloAv0XNxvrU4PoXdb7PgVBpYsKRT7tdnOGpokQj2n2xQSHvQVypd8xkHYloczdlmi9zvytM_Da8jnLdE0Td3mlnI-jlovrbzykD4YhS_ZcwIc_8c_WiHDeC3yAeTTx0EhuNh89HFSsmfod2BBaRgvf5SRpNwUjxd0PROvrGD_Hl0emtsC9YWdfULQBk3SKqJMV0x7Zv_Erj8JZoMzXbe8jv5bM4AmYqOPqznkXtAWAkedqLVYug0od9AgMIn-FFPpH4AvmHhhliMevIw74yEXCj28dxlHBZvyyqpbEwp8vr_kTjarjyp3s1p6QsYEoQ82vx-nQPiVFwWeGDsJcSmUD4FbtVIVCGY3p2cCtZHyRRWAK2LGgO32v3DhK7FSLKV-3f0LAafqdpZyGnqoF7pwn_RcpZz8l9tGctFl8Xd50Y1naLz8F08SlbJ197xdjzHLQ8r4jsVn5m9BkGbR0Kv3olXGRmtr5dkqGI-68_FCjkkqpwfOHg</p>
|
|||
|
|
<h2 id=""></h2>
|
|||
|
|
<pre class="hljs"><code><div><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getMetadataPreviewAction</span>(<span class="hljs-params">fileId: <span class="hljs-built_in">string</span></span>) </span>{
|
|||
|
|
<span class="hljs-keyword">const</span> session = <span class="hljs-keyword">await</span> auth();
|
|||
|
|
<span class="hljs-keyword">if</span> (!session?.user?.id) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Unauthorized"</span>);
|
|||
|
|
|
|||
|
|
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">`\n--- 🔍 Magic Fill Started for File ID: <span class="hljs-subst">${fileId}</span> ---`</span>);
|
|||
|
|
|
|||
|
|
<span class="hljs-keyword">try</span> {
|
|||
|
|
<span class="hljs-keyword">const</span> node = <span class="hljs-keyword">await</span> getFileNodeById(fileId);
|
|||
|
|
<span class="hljs-keyword">if</span> (!node || !node.oneDriveId) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"File not found"</span>);
|
|||
|
|
|
|||
|
|
<span class="hljs-comment">// 1. Get the token from our utility</span>
|
|||
|
|
<span class="hljs-keyword">const</span> token = <span class="hljs-keyword">await</span> getFreshAccessToken(session.user.id);
|
|||
|
|
|
|||
|
|
<span class="hljs-comment">// LOG: Just check the length to be sure it's not empty</span>
|
|||
|
|
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">`🔑 Token retrieved (Length: <span class="hljs-subst">${token.length}</span>)`</span>);
|
|||
|
|
|
|||
|
|
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">`📡 Fetching binary for: <span class="hljs-subst">${node.name}</span>...`</span>);
|
|||
|
|
|
|||
|
|
<span class="hljs-comment">// 2. Fetch the content from Microsoft Graph</span>
|
|||
|
|
<span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(
|
|||
|
|
<span class="hljs-string">`https://graph.microsoft.com/v1.0/me/drive/items/<span class="hljs-subst">${node.oneDriveId}</span>/content`</span>,
|
|||
|
|
{
|
|||
|
|
headers: {
|
|||
|
|
<span class="hljs-string">'Authorization'</span>: <span class="hljs-string">`Bearer <span class="hljs-subst">${token}</span>`</span>,
|
|||
|
|
<span class="hljs-string">'Accept'</span>: <span class="hljs-string">'*/*'</span>
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
<span class="hljs-keyword">if</span> (!response.ok) {
|
|||
|
|
<span class="hljs-comment">// If it fails here, we'll see the real reason from Microsoft</span>
|
|||
|
|
<span class="hljs-keyword">const</span> errorText = <span class="hljs-keyword">await</span> response.text();
|
|||
|
|
<span class="hljs-built_in">console</span>.error(<span class="hljs-string">"❌ Microsoft Graph Error Response:"</span>, errorText);
|
|||
|
|
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`OneDrive Download Failed: <span class="hljs-subst">${response.status}</span>`</span>);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
<span class="hljs-keyword">const</span> arrayBuffer = <span class="hljs-keyword">await</span> response.arrayBuffer();
|
|||
|
|
<span class="hljs-keyword">const</span> buffer = Buffer.from(arrayBuffer);
|
|||
|
|
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">`📦 Success! Downloaded <span class="hljs-subst">${buffer.length}</span> bytes.`</span>);
|
|||
|
|
|
|||
|
|
<span class="hljs-comment">// 3. Extract Metadata</span>
|
|||
|
|
<span class="hljs-keyword">const</span> extractedData = <span class="hljs-keyword">await</span> extractMetadata(buffer, node.name);
|
|||
|
|
|
|||
|
|
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"✅ RAW DATA EXTRACTED:"</span>);
|
|||
|
|
<span class="hljs-built_in">console</span>.dir(extractedData, { depth: <span class="hljs-literal">null</span>, colors: <span class="hljs-literal">true</span> });
|
|||
|
|
|
|||
|
|
<span class="hljs-keyword">return</span> { success: <span class="hljs-literal">true</span>, data: extractedData };
|
|||
|
|
|
|||
|
|
} <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
|
|||
|
|
<span class="hljs-built_in">console</span>.error(<span class="hljs-string">"❌ Magic Fill Error:"</span>, error.message);
|
|||
|
|
<span class="hljs-keyword">return</span> { success: <span class="hljs-literal">false</span>, error: error.message };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</div></code></pre>
|
|||
|
|
<h2 id="dump-of-metadata-for-an-image">Dump of metadata for an image</h2>
|
|||
|
|
<pre class="hljs"><code><div>npm run dev
|
|||
|
|
|
|||
|
|
> 124_webcalibre2@0.1.0 dev
|
|||
|
|
> next dev
|
|||
|
|
|
|||
|
|
▲ Next.js 16.1.1 (Turbopack)
|
|||
|
|
- Local: http://localhost:3000
|
|||
|
|
- Network: http://192.168.1.100:3000
|
|||
|
|
- Environments: .env.local, .env
|
|||
|
|
- Experiments (use with caution):
|
|||
|
|
· serverActions
|
|||
|
|
|
|||
|
|
✓ Starting...
|
|||
|
|
✓ Ready <span class="hljs-keyword">in</span> 380ms
|
|||
|
|
✅ Prisma 7 connected to PostgreSQL successfully
|
|||
|
|
GET /dashboard 200 <span class="hljs-keyword">in</span> 643ms (compile: 139ms, proxy.ts: 115ms, render: 388ms)
|
|||
|
|
✅ Prisma 7 connected to PostgreSQL successfully
|
|||
|
|
GET /update/d3ccd68a-0493-487d-9273-5fa8ff6a6f9a 200 <span class="hljs-keyword">in</span> 329ms (compile: 274ms, proxy.ts: 19ms, render: 37ms)
|
|||
|
|
|
|||
|
|
--- 🔍 Magic Fill Started <span class="hljs-keyword">for</span> File ID: d3ccd68a-0493-487d-9273-5fa8ff6a6f9a ---
|
|||
|
|
🔄 Access token expired. Refreshing <span class="hljs-keyword">for</span> user: cc6f11ff-549d-40fa-883f-7886ee6487cf
|
|||
|
|
✅ Prisma 7 connected to PostgreSQL successfully
|
|||
|
|
🔑 Token retrieved (Length: 1484)
|
|||
|
|
📡 Fetching binary <span class="hljs-keyword">for</span>: IMG_2360.jpeg...
|
|||
|
|
📦 Success! Downloaded 1233596 bytes.
|
|||
|
|
📸 FULL RAW EXIF DATA: {
|
|||
|
|
<span class="hljs-string">"bigEndian"</span>: <span class="hljs-literal">true</span>,
|
|||
|
|
<span class="hljs-string">"Image"</span>: {
|
|||
|
|
<span class="hljs-string">"Make"</span>: <span class="hljs-string">"Apple"</span>,
|
|||
|
|
<span class="hljs-string">"Model"</span>: <span class="hljs-string">"iPhone 14"</span>,
|
|||
|
|
<span class="hljs-string">"Orientation"</span>: 1,
|
|||
|
|
<span class="hljs-string">"XResolution"</span>: 72,
|
|||
|
|
<span class="hljs-string">"YResolution"</span>: 72,
|
|||
|
|
<span class="hljs-string">"ResolutionUnit"</span>: 2,
|
|||
|
|
<span class="hljs-string">"Software"</span>: <span class="hljs-string">"18.6.2"</span>,
|
|||
|
|
<span class="hljs-string">"DateTime"</span>: <span class="hljs-string">"2025-10-03T08:02:39.000Z"</span>,
|
|||
|
|
<span class="hljs-string">"HostComputer"</span>: <span class="hljs-string">"iPhone 14"</span>,
|
|||
|
|
<span class="hljs-string">"YCbCrPositioning"</span>: 1,
|
|||
|
|
<span class="hljs-string">"ExifTag"</span>: 228,
|
|||
|
|
<span class="hljs-string">"GPSTag"</span>: 2586
|
|||
|
|
},
|
|||
|
|
<span class="hljs-string">"Thumbnail"</span>: {
|
|||
|
|
<span class="hljs-string">"Compression"</span>: 6,
|
|||
|
|
<span class="hljs-string">"XResolution"</span>: 72,
|
|||
|
|
<span class="hljs-string">"YResolution"</span>: 72,
|
|||
|
|
<span class="hljs-string">"ResolutionUnit"</span>: 2,
|
|||
|
|
<span class="hljs-string">"JPEGInterchangeFormat"</span>: 2990,
|
|||
|
|
<span class="hljs-string">"JPEGInterchangeFormatLength"</span>: 3322
|
|||
|
|
},
|
|||
|
|
<span class="hljs-string">"Photo"</span>: {
|
|||
|
|
<span class="hljs-string">"ExposureTime"</span>: 0.025,
|
|||
|
|
<span class="hljs-string">"FNumber"</span>: 1.5,
|
|||
|
|
<span class="hljs-string">"ExposureProgram"</span>: 2,
|
|||
|
|
<span class="hljs-string">"ISOSpeedRatings"</span>: 500,
|
|||
|
|
<span class="hljs-string">"ExifVersion"</span>: {
|
|||
|
|
<span class="hljs-string">"type"</span>: <span class="hljs-string">"Buffer"</span>,
|
|||
|
|
<span class="hljs-string">"data"</span>: [
|
|||
|
|
48,
|
|||
|
|
50,
|
|||
|
|
51,
|
|||
|
|
50
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
<span class="hljs-string">"DateTimeOriginal"</span>: <span class="hljs-string">"2025-10-03T08:02:39.000Z"</span>,
|
|||
|
|
<span class="hljs-string">"DateTimeDigitized"</span>: <span class="hljs-string">"2025-10-03T08:02:39.000Z"</span>,
|
|||
|
|
<span class="hljs-string">"OffsetTime"</span>: <span class="hljs-string">"+02:00"</span>,
|
|||
|
|
<span class="hljs-string">"OffsetTimeOriginal"</span>: <span class="hljs-string">"+02:00"</span>,
|
|||
|
|
<span class="hljs-string">"OffsetTimeDigitized"</span>: <span class="hljs-string">"+02:00"</span>,
|
|||
|
|
<span class="hljs-string">"ComponentsConfiguration"</span>: {
|
|||
|
|
<span class="hljs-string">"type"</span>: <span class="hljs-string">"Buffer"</span>,
|
|||
|
|
<span class="hljs-string">"data"</span>: [
|
|||
|
|
1,
|
|||
|
|
2,
|
|||
|
|
3,
|
|||
|
|
0
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
<span class="hljs-string">"ShutterSpeedValue"</span>: 5.327015336217066,
|
|||
|
|
<span class="hljs-string">"ApertureValue"</span>: 1.1699250021066825,
|
|||
|
|
<span class="hljs-string">"BrightnessValue"</span>: -0.4198113650227582,
|
|||
|
|
<span class="hljs-string">"ExposureBiasValue"</span>: 0,
|
|||
|
|
<span class="hljs-string">"MeteringMode"</span>: 5,
|
|||
|
|
<span class="hljs-string">"Flash"</span>: 16,
|
|||
|
|
<span class="hljs-string">"FocalLength"</span>: 5.7,
|
|||
|
|
<span class="hljs-string">"SubjectArea"</span>: [
|
|||
|
|
2006,
|
|||
|
|
1506,
|
|||
|
|
2213,
|
|||
|
|
1327
|
|||
|
|
],
|
|||
|
|
<span class="hljs-string">"MakerNote"</span>: {
|
|||
|
|
<span class="hljs-string">"type"</span>: <span class="hljs-string">"Buffer"</span>,
|
|||
|
|
<span class="hljs-string">"data"</span>: [
|
|||
|
|
65,
|
|||
|
|
112,
|
|||
|
|
112,
|
|||
|
|
108,
|
|||
|
|
101,
|
|||
|
|
32,
|
|||
|
|
105,
|
|||
|
|
79,
|
|||
|
|
83,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
77,
|
|||
|
|
77,
|
|||
|
|
0,
|
|||
|
|
49,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
15,
|
|||
|
|
0,
|
|||
|
|
2,
|
|||
|
|
0,
|
|||
|
|
7,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
2,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
2,
|
|||
|
|
96,
|
|||
|
|
0,
|
|||
|
|
3,
|
|||
|
|
0,
|
|||
|
|
7,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
104,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
96,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
149,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
152,
|
|||
|
|
0,
|
|||
|
|
7,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
8,
|
|||
|
|
0,
|
|||
|
|
10,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
3,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
200,
|
|||
|
|
0,
|
|||
|
|
12,
|
|||
|
|
0,
|
|||
|
|
10,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
2,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
224,
|
|||
|
|
0,
|
|||
|
|
13,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
39,
|
|||
|
|
0,
|
|||
|
|
14,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
16,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
20,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
12,
|
|||
|
|
0,
|
|||
|
|
22,
|
|||
|
|
0,
|
|||
|
|
7,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
72,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
240,
|
|||
|
|
0,
|
|||
|
|
23,
|
|||
|
|
0,
|
|||
|
|
16,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
56,
|
|||
|
|
0,
|
|||
|
|
25,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
32,
|
|||
|
|
2,
|
|||
|
|
0,
|
|||
|
|
26,
|
|||
|
|
0,
|
|||
|
|
2,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
64,
|
|||
|
|
0,
|
|||
|
|
31,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
32,
|
|||
|
|
0,
|
|||
|
|
2,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
37,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
70,
|
|||
|
|
0,
|
|||
|
|
33,
|
|||
|
|
0,
|
|||
|
|
10,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
107,
|
|||
|
|
0,
|
|||
|
|
35,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
2,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
115,
|
|||
|
|
0,
|
|||
|
|
37,
|
|||
|
|
0,
|
|||
|
|
16,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
123,
|
|||
|
|
0,
|
|||
|
|
38,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
3,
|
|||
|
|
0,
|
|||
|
|
39,
|
|||
|
|
0,
|
|||
|
|
10,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
131,
|
|||
|
|
0,
|
|||
|
|
43,
|
|||
|
|
0,
|
|||
|
|
2,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
37,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
139,
|
|||
|
|
0,
|
|||
|
|
45,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
30,
|
|||
|
|
150,
|
|||
|
|
0,
|
|||
|
|
46,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
47,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
48,
|
|||
|
|
0,
|
|||
|
|
48,
|
|||
|
|
0,
|
|||
|
|
10,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
176,
|
|||
|
|
0,
|
|||
|
|
54,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
42,
|
|||
|
|
0,
|
|||
|
|
55,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
58,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
59,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
60,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
63,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
64,
|
|||
|
|
0,
|
|||
|
|
7,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
74,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
184,
|
|||
|
|
0,
|
|||
|
|
65,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
67,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
68,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
69,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
70,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
74,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
2,
|
|||
|
|
0,
|
|||
|
|
77,
|
|||
|
|
0,
|
|||
|
|
10,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
2,
|
|||
|
|
0,
|
|||
|
|
78,
|
|||
|
|
0,
|
|||
|
|
7,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
121,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
10,
|
|||
|
|
0,
|
|||
|
|
79,
|
|||
|
|
0,
|
|||
|
|
7,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
43,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
131,
|
|||
|
|
0,
|
|||
|
|
82,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
11,
|
|||
|
|
0,
|
|||
|
|
83,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
85,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
88,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
7,
|
|||
|
|
3,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
239,
|
|||
|
|
1,
|
|||
|
|
205,
|
|||
|
|
1,
|
|||
|
|
169,
|
|||
|
|
1,
|
|||
|
|
135,
|
|||
|
|
1,
|
|||
|
|
103,
|
|||
|
|
1,
|
|||
|
|
73,
|
|||
|
|
1,
|
|||
|
|
45,
|
|||
|
|
1,
|
|||
|
|
19,
|
|||
|
|
1,
|
|||
|
|
251,
|
|||
|
|
0,
|
|||
|
|
228,
|
|||
|
|
0,
|
|||
|
|
207,
|
|||
|
|
0,
|
|||
|
|
188,
|
|||
|
|
0,
|
|||
|
|
172,
|
|||
|
|
0,
|
|||
|
|
158,
|
|||
|
|
0,
|
|||
|
|
146,
|
|||
|
|
0,
|
|||
|
|
136,
|
|||
|
|
0,
|
|||
|
|
54,
|
|||
|
|
2,
|
|||
|
|
17,
|
|||
|
|
2,
|
|||
|
|
232,
|
|||
|
|
1,
|
|||
|
|
193,
|
|||
|
|
1,
|
|||
|
|
155,
|
|||
|
|
1,
|
|||
|
|
118,
|
|||
|
|
1,
|
|||
|
|
85,
|
|||
|
|
1,
|
|||
|
|
55,
|
|||
|
|
1,
|
|||
|
|
26,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
231,
|
|||
|
|
0,
|
|||
|
|
209,
|
|||
|
|
0,
|
|||
|
|
189,
|
|||
|
|
0,
|
|||
|
|
173,
|
|||
|
|
0,
|
|||
|
|
160,
|
|||
|
|
0,
|
|||
|
|
147,
|
|||
|
|
0,
|
|||
|
|
125,
|
|||
|
|
2,
|
|||
|
|
89,
|
|||
|
|
2,
|
|||
|
|
47,
|
|||
|
|
2,
|
|||
|
|
2,
|
|||
|
|
2,
|
|||
|
|
216,
|
|||
|
|
1,
|
|||
|
|
175,
|
|||
|
|
1,
|
|||
|
|
137,
|
|||
|
|
1,
|
|||
|
|
99,
|
|||
|
|
1,
|
|||
|
|
64,
|
|||
|
|
1,
|
|||
|
|
33,
|
|||
|
|
1,
|
|||
|
|
4,
|
|||
|
|
1,
|
|||
|
|
233,
|
|||
|
|
0,
|
|||
|
|
210,
|
|||
|
|
0,
|
|||
|
|
190,
|
|||
|
|
0,
|
|||
|
|
173,
|
|||
|
|
0,
|
|||
|
|
159,
|
|||
|
|
0,
|
|||
|
|
8,
|
|||
|
|
3,
|
|||
|
|
217,
|
|||
|
|
2,
|
|||
|
|
170,
|
|||
|
|
2,
|
|||
|
|
120,
|
|||
|
|
2,
|
|||
|
|
65,
|
|||
|
|
2,
|
|||
|
|
13,
|
|||
|
|
2,
|
|||
|
|
218,
|
|||
|
|
1,
|
|||
|
|
171,
|
|||
|
|
1,
|
|||
|
|
125,
|
|||
|
|
1,
|
|||
|
|
83,
|
|||
|
|
1,
|
|||
|
|
44,
|
|||
|
|
1,
|
|||
|
|
10,
|
|||
|
|
1,
|
|||
|
|
235,
|
|||
|
|
0,
|
|||
|
|
210,
|
|||
|
|
0,
|
|||
|
|
188,
|
|||
|
|
0,
|
|||
|
|
170,
|
|||
|
|
0,
|
|||
|
|
252,
|
|||
|
|
2,
|
|||
|
|
242,
|
|||
|
|
2,
|
|||
|
|
210,
|
|||
|
|
2,
|
|||
|
|
170,
|
|||
|
|
2,
|
|||
|
|
131,
|
|||
|
|
2,
|
|||
|
|
81,
|
|||
|
|
2,
|
|||
|
|
30,
|
|||
|
|
2,
|
|||
|
|
230,
|
|||
|
|
1,
|
|||
|
|
175,
|
|||
|
|
1,
|
|||
|
|
124,
|
|||
|
|
1,
|
|||
|
|
77,
|
|||
|
|
1,
|
|||
|
|
36,
|
|||
|
|
1,
|
|||
|
|
255,
|
|||
|
|
0,
|
|||
|
|
225,
|
|||
|
|
0,
|
|||
|
|
199,
|
|||
|
|
0,
|
|||
|
|
178,
|
|||
|
|
0,
|
|||
|
|
69,
|
|||
|
|
1,
|
|||
|
|
93,
|
|||
|
|
1,
|
|||
|
|
121,
|
|||
|
|
1,
|
|||
|
|
168,
|
|||
|
|
1,
|
|||
|
|
208,
|
|||
|
|
1,
|
|||
|
|
206,
|
|||
|
|
1,
|
|||
|
|
193,
|
|||
|
|
1,
|
|||
|
|
168,
|
|||
|
|
1,
|
|||
|
|
135,
|
|||
|
|
1,
|
|||
|
|
107,
|
|||
|
|
1,
|
|||
|
|
69,
|
|||
|
|
1,
|
|||
|
|
32,
|
|||
|
|
1,
|
|||
|
|
251,
|
|||
|
|
0,
|
|||
|
|
220,
|
|||
|
|
0,
|
|||
|
|
196,
|
|||
|
|
0,
|
|||
|
|
176,
|
|||
|
|
0,
|
|||
|
|
183,
|
|||
|
|
0,
|
|||
|
|
185,
|
|||
|
|
0,
|
|||
|
|
182,
|
|||
|
|
0,
|
|||
|
|
183,
|
|||
|
|
0,
|
|||
|
|
181,
|
|||
|
|
0,
|
|||
|
|
182,
|
|||
|
|
0,
|
|||
|
|
175,
|
|||
|
|
0,
|
|||
|
|
179,
|
|||
|
|
0,
|
|||
|
|
192,
|
|||
|
|
0,
|
|||
|
|
183,
|
|||
|
|
0,
|
|||
|
|
178,
|
|||
|
|
0,
|
|||
|
|
169,
|
|||
|
|
0,
|
|||
|
|
158,
|
|||
|
|
0,
|
|||
|
|
143,
|
|||
|
|
0,
|
|||
|
|
139,
|
|||
|
|
0,
|
|||
|
|
131,
|
|||
|
|
0,
|
|||
|
|
173,
|
|||
|
|
0,
|
|||
|
|
179,
|
|||
|
|
0,
|
|||
|
|
173,
|
|||
|
|
0,
|
|||
|
|
185,
|
|||
|
|
0,
|
|||
|
|
187,
|
|||
|
|
0,
|
|||
|
|
231,
|
|||
|
|
0,
|
|||
|
|
145,
|
|||
|
|
1,
|
|||
|
|
164,
|
|||
|
|
1,
|
|||
|
|
147,
|
|||
|
|
1,
|
|||
|
|
107,
|
|||
|
|
1,
|
|||
|
|
38,
|
|||
|
|
1,
|
|||
|
|
157,
|
|||
|
|
0,
|
|||
|
|
139,
|
|||
|
|
0,
|
|||
|
|
166,
|
|||
|
|
0,
|
|||
|
|
169,
|
|||
|
|
0,
|
|||
|
|
152,
|
|||
|
|
0,
|
|||
|
|
36,
|
|||
|
|
0,
|
|||
|
|
55,
|
|||
|
|
0,
|
|||
|
|
64,
|
|||
|
|
0,
|
|||
|
|
58,
|
|||
|
|
0,
|
|||
|
|
57,
|
|||
|
|
0,
|
|||
|
|
55,
|
|||
|
|
0,
|
|||
|
|
56,
|
|||
|
|
0,
|
|||
|
|
50,
|
|||
|
|
0,
|
|||
|
|
45,
|
|||
|
|
0,
|
|||
|
|
42,
|
|||
|
|
0,
|
|||
|
|
37,
|
|||
|
|
0,
|
|||
|
|
27,
|
|||
|
|
0,
|
|||
|
|
25,
|
|||
|
|
0,
|
|||
|
|
23,
|
|||
|
|
0,
|
|||
|
|
30,
|
|||
|
|
0,
|
|||
|
|
43,
|
|||
|
|
0,
|
|||
|
|
10,
|
|||
|
|
0,
|
|||
|
|
11,
|
|||
|
|
0,
|
|||
|
|
12,
|
|||
|
|
0,
|
|||
|
|
14,
|
|||
|
|
0,
|
|||
|
|
19,
|
|||
|
|
0,
|
|||
|
|
23,
|
|||
|
|
0,
|
|||
|
|
25,
|
|||
|
|
0,
|
|||
|
|
24,
|
|||
|
|
0,
|
|||
|
|
23,
|
|||
|
|
0,
|
|||
|
|
21,
|
|||
|
|
0,
|
|||
|
|
20,
|
|||
|
|
0,
|
|||
|
|
18,
|
|||
|
|
0,
|
|||
|
|
17,
|
|||
|
|
0,
|
|||
|
|
16,
|
|||
|
|
0,
|
|||
|
|
16,
|
|||
|
|
0,
|
|||
|
|
16,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
0,
|
|||
|
|
7,
|
|||
|
|
0,
|
|||
|
|
7,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
11,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
12,
|
|||
|
|
0,
|
|||
|
|
12,
|
|||
|
|
0,
|
|||
|
|
15,
|
|||
|
|
0,
|
|||
|
|
13,
|
|||
|
|
0,
|
|||
|
|
13,
|
|||
|
|
0,
|
|||
|
|
12,
|
|||
|
|
0,
|
|||
|
|
11,
|
|||
|
|
0,
|
|||
|
|
10,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
10,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
0,
|
|||
|
|
7,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
10,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
0,
|
|||
|
|
7,
|
|||
|
|
0,
|
|||
|
|
7,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
0,
|
|||
|
|
7,
|
|||
|
|
0,
|
|||
|
|
8,
|
|||
|
|
0,
|
|||
|
|
8,
|
|||
|
|
0,
|
|||
|
|
8,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
3,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
0,
|
|||
|
|
7,
|
|||
|
|
0,
|
|||
|
|
6,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
3,
|
|||
|
|
0,
|
|||
|
|
3,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
4,
|
|||
|
|
0,
|
|||
|
|
5,
|
|||
|
|
0,
|
|||
|
|
98,
|
|||
|
|
112,
|
|||
|
|
108,
|
|||
|
|
105,
|
|||
|
|
115,
|
|||
|
|
116,
|
|||
|
|
48,
|
|||
|
|
48,
|
|||
|
|
212,
|
|||
|
|
1,
|
|||
|
|
2,
|
|||
|
|
3,
|
|||
|
|
4,
|
|||
|
|
5,
|
|||
|
|
6,
|
|||
|
|
7,
|
|||
|
|
8,
|
|||
|
|
85,
|
|||
|
|
102,
|
|||
|
|
108,
|
|||
|
|
97,
|
|||
|
|
103,
|
|||
|
|
115,
|
|||
|
|
85,
|
|||
|
|
118,
|
|||
|
|
97,
|
|||
|
|
108,
|
|||
|
|
117,
|
|||
|
|
101,
|
|||
|
|
89,
|
|||
|
|
116,
|
|||
|
|
105,
|
|||
|
|
109,
|
|||
|
|
101,
|
|||
|
|
115,
|
|||
|
|
99,
|
|||
|
|
97,
|
|||
|
|
108,
|
|||
|
|
101,
|
|||
|
|
85,
|
|||
|
|
101,
|
|||
|
|
112,
|
|||
|
|
111,
|
|||
|
|
99,
|
|||
|
|
104,
|
|||
|
|
16,
|
|||
|
|
1,
|
|||
|
|
19,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
62,
|
|||
|
|
142,
|
|||
|
|
239,
|
|||
|
|
162,
|
|||
|
|
85,
|
|||
|
|
109,
|
|||
|
|
18,
|
|||
|
|
59,
|
|||
|
|
154,
|
|||
|
|
202,
|
|||
|
|
0,
|
|||
|
|
16,
|
|||
|
|
0,
|
|||
|
|
8,
|
|||
|
|
17,
|
|||
|
|
23,
|
|||
|
|
29,
|
|||
|
|
39,
|
|||
|
|
45,
|
|||
|
|
47,
|
|||
|
|
56,
|
|||
|
|
61,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
63,
|
|||
|
|
255,
|
|||
|
|
255,
|
|||
|
|
84,
|
|||
|
|
169,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
168,
|
|||
|
|
221,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
54,
|
|||
|
|
115,
|
|||
|
|
0,
|
|||
|
|
9,
|
|||
|
|
142,
|
|||
|
|
158,
|
|||
|
|
255,
|
|||
|
|
255,
|
|||
|
|
244,
|
|||
|
|
153,
|
|||
|
|
0,
|
|||
|
|
2,
|
|||
|
|
69,
|
|||
|
|
230,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
91,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
128,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
81,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
98,
|
|||
|
|
112,
|
|||
|
|
108,
|
|||
|
|
105,
|
|||
|
|
115,
|
|||
|
|
116,
|
|||
|
|
48,
|
|||
|
|
48,
|
|||
|
|
95,
|
|||
|
|
16,
|
|||
|
|
28,
|
|||
|
|
65,
|
|||
|
|
88,
|
|||
|
|
118,
|
|||
|
|
67,
|
|||
|
|
67,
|
|||
|
|
48,
|
|||
|
|
55,
|
|||
|
|
76,
|
|||
|
|
49,
|
|||
|
|
117,
|
|||
|
|
79,
|
|||
|
|
73,
|
|||
|
|
113,
|
|||
|
|
48,
|
|||
|
|
73,
|
|||
|
|
98,
|
|||
|
|
101,
|
|||
|
|
51,
|
|||
|
|
76,
|
|||
|
|
121,
|
|||
|
|
112,
|
|||
|
|
112,
|
|||
|
|
98,
|
|||
|
|
98,
|
|||
|
|
86,
|
|||
|
|
43,
|
|||
|
|
108,
|
|||
|
|
113,
|
|||
|
|
8,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
39,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
80,
|
|||
|
|
32,
|
|||
|
|
4,
|
|||
|
|
113,
|
|||
|
|
55,
|
|||
|
|
53,
|
|||
|
|
48,
|
|||
|
|
110,
|
|||
|
|
0,
|
|||
|
|
55,
|
|||
|
|
67,
|
|||
|
|
65,
|
|||
|
|
54,
|
|||
|
|
53,
|
|||
|
|
50,
|
|||
|
|
48,
|
|||
|
|
68,
|
|||
|
|
45,
|
|||
|
|
67,
|
|||
|
|
50,
|
|||
|
|
49,
|
|||
|
|
55,
|
|||
|
|
45,
|
|||
|
|
52,
|
|||
|
|
56,
|
|||
|
|
68,
|
|||
|
|
49,
|
|||
|
|
45,
|
|||
|
|
56,
|
|||
|
|
48,
|
|||
|
|
49,
|
|||
|
|
67,
|
|||
|
|
45,
|
|||
|
|
50,
|
|||
|
|
69,
|
|||
|
|
49,
|
|||
|
|
57,
|
|||
|
|
54,
|
|||
|
|
69,
|
|||
|
|
66,
|
|||
|
|
66,
|
|||
|
|
68,
|
|||
|
|
52,
|
|||
|
|
65,
|
|||
|
|
50,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
249,
|
|||
|
|
65,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
39,
|
|||
|
|
154,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
55,
|
|||
|
|
16,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
27,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
20,
|
|||
|
|
142,
|
|||
|
|
0,
|
|||
|
|
2,
|
|||
|
|
96,
|
|||
|
|
195,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
19,
|
|||
|
|
224,
|
|||
|
|
51,
|
|||
|
|
67,
|
|||
|
|
53,
|
|||
|
|
51,
|
|||
|
|
52,
|
|||
|
|
69,
|
|||
|
|
66,
|
|||
|
|
69,
|
|||
|
|
45,
|
|||
|
|
52,
|
|||
|
|
65,
|
|||
|
|
53,
|
|||
|
|
51,
|
|||
|
|
45,
|
|||
|
|
52,
|
|||
|
|
55,
|
|||
|
|
69,
|
|||
|
|
70,
|
|||
|
|
45,
|
|||
|
|
57,
|
|||
|
|
49,
|
|||
|
|
55,
|
|||
|
|
65,
|
|||
|
|
45,
|
|||
|
|
66,
|
|||
|
|
55,
|
|||
|
|
56,
|
|||
|
|
53,
|
|||
|
|
57,
|
|||
|
|
55,
|
|||
|
|
51,
|
|||
|
|
55,
|
|||
|
|
50,
|
|||
|
|
53,
|
|||
|
|
57,
|
|||
|
|
52,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
98,
|
|||
|
|
112,
|
|||
|
|
108,
|
|||
|
|
105,
|
|||
|
|
115,
|
|||
|
|
116,
|
|||
|
|
48,
|
|||
|
|
48,
|
|||
|
|
212,
|
|||
|
|
1,
|
|||
|
|
2,
|
|||
|
|
3,
|
|||
|
|
4,
|
|||
|
|
5,
|
|||
|
|
6,
|
|||
|
|
6,
|
|||
|
|
7,
|
|||
|
|
81,
|
|||
|
|
51,
|
|||
|
|
81,
|
|||
|
|
49,
|
|||
|
|
81,
|
|||
|
|
50,
|
|||
|
|
81,
|
|||
|
|
48,
|
|||
|
|
16,
|
|||
|
|
0,
|
|||
|
|
34,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
16,
|
|||
|
|
1,
|
|||
|
|
8,
|
|||
|
|
17,
|
|||
|
|
19,
|
|||
|
|
21,
|
|||
|
|
23,
|
|||
|
|
25,
|
|||
|
|
27,
|
|||
|
|
32,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
8,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
34,
|
|||
|
|
0,
|
|||
|
|
2,
|
|||
|
|
38,
|
|||
|
|
42,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
19,
|
|||
|
|
59,
|
|||
|
|
98,
|
|||
|
|
112,
|
|||
|
|
108,
|
|||
|
|
105,
|
|||
|
|
115,
|
|||
|
|
116,
|
|||
|
|
48,
|
|||
|
|
48,
|
|||
|
|
210,
|
|||
|
|
1,
|
|||
|
|
2,
|
|||
|
|
3,
|
|||
|
|
4,
|
|||
|
|
81,
|
|||
|
|
49,
|
|||
|
|
81,
|
|||
|
|
50,
|
|||
|
|
16,
|
|||
|
|
3,
|
|||
|
|
162,
|
|||
|
|
5,
|
|||
|
|
10,
|
|||
|
|
210,
|
|||
|
|
6,
|
|||
|
|
7,
|
|||
|
|
8,
|
|||
|
|
9,
|
|||
|
|
83,
|
|||
|
|
50,
|
|||
|
|
46,
|
|||
|
|
49,
|
|||
|
|
83,
|
|||
|
|
50,
|
|||
|
|
46,
|
|||
|
|
50,
|
|||
|
|
35,
|
|||
|
|
64,
|
|||
|
|
157,
|
|||
|
|
225,
|
|||
|
|
32,
|
|||
|
|
96,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
35,
|
|||
|
|
64,
|
|||
|
|
124,
|
|||
|
|
160,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
210,
|
|||
|
|
6,
|
|||
|
|
7,
|
|||
|
|
11,
|
|||
|
|
12,
|
|||
|
|
35,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
35,
|
|||
|
|
64,
|
|||
|
|
72,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
8,
|
|||
|
|
13,
|
|||
|
|
15,
|
|||
|
|
17,
|
|||
|
|
19,
|
|||
|
|
22,
|
|||
|
|
27,
|
|||
|
|
31,
|
|||
|
|
35,
|
|||
|
|
44,
|
|||
|
|
53,
|
|||
|
|
58,
|
|||
|
|
67,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
13,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
76,
|
|||
|
|
98,
|
|||
|
|
112,
|
|||
|
|
108,
|
|||
|
|
105,
|
|||
|
|
115,
|
|||
|
|
116,
|
|||
|
|
48,
|
|||
|
|
48,
|
|||
|
|
16,
|
|||
|
|
0,
|
|||
|
|
8,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
1,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
10
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
<span class="hljs-string">"SubSecTimeOriginal"</span>: <span class="hljs-string">"714"</span>,
|
|||
|
|
<span class="hljs-string">"SubSecTimeDigitized"</span>: <span class="hljs-string">"714"</span>,
|
|||
|
|
<span class="hljs-string">"FlashpixVersion"</span>: {
|
|||
|
|
<span class="hljs-string">"type"</span>: <span class="hljs-string">"Buffer"</span>,
|
|||
|
|
<span class="hljs-string">"data"</span>: [
|
|||
|
|
48,
|
|||
|
|
49,
|
|||
|
|
48,
|
|||
|
|
48
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
<span class="hljs-string">"ColorSpace"</span>: 65535,
|
|||
|
|
<span class="hljs-string">"PixelXDimension"</span>: 4032,
|
|||
|
|
<span class="hljs-string">"PixelYDimension"</span>: 3024,
|
|||
|
|
<span class="hljs-string">"SensingMethod"</span>: 2,
|
|||
|
|
<span class="hljs-string">"SceneType"</span>: {
|
|||
|
|
<span class="hljs-string">"type"</span>: <span class="hljs-string">"Buffer"</span>,
|
|||
|
|
<span class="hljs-string">"data"</span>: [
|
|||
|
|
1
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
<span class="hljs-string">"ExposureMode"</span>: 0,
|
|||
|
|
<span class="hljs-string">"WhiteBalance"</span>: 0,
|
|||
|
|
<span class="hljs-string">"FocalLengthIn35mmFilm"</span>: 26,
|
|||
|
|
<span class="hljs-string">"SceneCaptureType"</span>: 0,
|
|||
|
|
<span class="hljs-string">"LensSpecification"</span>: [
|
|||
|
|
1.5399999618512084,
|
|||
|
|
5.699999809263318,
|
|||
|
|
1.5,
|
|||
|
|
2.4
|
|||
|
|
],
|
|||
|
|
<span class="hljs-string">"LensMake"</span>: <span class="hljs-string">"Apple"</span>,
|
|||
|
|
<span class="hljs-string">"LensModel"</span>: <span class="hljs-string">"iPhone 14 back dual wide camera 5.7mm f/1.5"</span>,
|
|||
|
|
<span class="hljs-string">"CompositeImage"</span>: 2
|
|||
|
|
},
|
|||
|
|
<span class="hljs-string">"GPSInfo"</span>: {
|
|||
|
|
<span class="hljs-string">"GPSLatitudeRef"</span>: <span class="hljs-string">"N"</span>,
|
|||
|
|
<span class="hljs-string">"GPSLatitude"</span>: [
|
|||
|
|
42,
|
|||
|
|
42,
|
|||
|
|
22.91
|
|||
|
|
],
|
|||
|
|
<span class="hljs-string">"GPSLongitudeRef"</span>: <span class="hljs-string">"W"</span>,
|
|||
|
|
<span class="hljs-string">"GPSLongitude"</span>: [
|
|||
|
|
7,
|
|||
|
|
1,
|
|||
|
|
50.76
|
|||
|
|
],
|
|||
|
|
<span class="hljs-string">"GPSAltitudeRef"</span>: 0,
|
|||
|
|
<span class="hljs-string">"GPSAltitude"</span>: 1265.3340248962656,
|
|||
|
|
<span class="hljs-string">"GPSTimeStamp"</span>: [
|
|||
|
|
6,
|
|||
|
|
2,
|
|||
|
|
39
|
|||
|
|
],
|
|||
|
|
<span class="hljs-string">"GPSSpeedRef"</span>: <span class="hljs-string">"K"</span>,
|
|||
|
|
<span class="hljs-string">"GPSSpeed"</span>: 0,
|
|||
|
|
<span class="hljs-string">"GPSImgDirectionRef"</span>: <span class="hljs-string">"T"</span>,
|
|||
|
|
<span class="hljs-string">"GPSImgDirection"</span>: 147.2551498127341,
|
|||
|
|
<span class="hljs-string">"GPSDestBearingRef"</span>: <span class="hljs-string">"T"</span>,
|
|||
|
|
<span class="hljs-string">"GPSDestBearing"</span>: 147.2551498127341,
|
|||
|
|
<span class="hljs-string">"GPSDateStamp"</span>: <span class="hljs-string">"2025:10:03"</span>,
|
|||
|
|
<span class="hljs-string">"GPSHPositioningError"</span>: 4.748651528267963
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
✅ RAW DATA EXTRACTED:
|
|||
|
|
{
|
|||
|
|
<span class="hljs-built_in">type</span>: <span class="hljs-string">'IMAGE (JPEG)'</span>,
|
|||
|
|
dimensions: <span class="hljs-string">'4032x3024'</span>,
|
|||
|
|
title: <span class="hljs-string">'IMG_2360.jpeg'</span>
|
|||
|
|
}
|
|||
|
|
POST /update/d3ccd68a-0493-487d-9273-5fa8ff6a6f9a 200 <span class="hljs-keyword">in</span> 4.0s (compile: 9ms, proxy.ts: 19ms, render: 4.0s)
|
|||
|
|
</div></code></pre>
|
|||
|
|
<h2 id="where-the-code-should-exist">Where the code should exist</h2>
|
|||
|
|
<p>Two questions</p>
|
|||
|
|
<ol>
|
|||
|
|
<li>should the
|
|||
|
|
// 2. Fetch the content from Microsoft Graph
|
|||
|
|
const response = await fetch(
|
|||
|
|
<code>https://graph.microsoft.com/v1.0/me/drive/items/${node.oneDriveId}/content</code>,
|
|||
|
|
{
|
|||
|
|
headers: {
|
|||
|
|
'Authorization': <code>Bearer ${token}</code>,
|
|||
|
|
'Accept': '<em>/</em>'
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
Should this be in the services/onedrive.ts to be compliant with the Data Access Layer (DAL) that nextjs recomments?</li>
|
|||
|
|
<li>where should this getMetadataFromJson(buffer: Buffer) get placed , I think it should be placed in lib/metadata-extractor and exported from there, what do you think?</li>
|
|||
|
|
</ol>
|
|||
|
|
<p>Those are excellent architectural questions. You're moving from "making it work" to "making it scale," which is exactly where you want to be.</p>
|
|||
|
|
<h3 id="1-where-to-put-the-fetch-call">1. Where to put the fetch call?</h3>
|
|||
|
|
<p><strong>Yes, it absolutely belongs in <code>services/onedrive.ts</code>.</strong></p>
|
|||
|
|
<p>Next.js (and general clean architecture) recommends a <strong>Data Access Layer (DAL)</strong> to decouple your business logic from the specific implementation of your data source.</p>
|
|||
|
|
<p><strong>Why this is better:</strong></p>
|
|||
|
|
<ul>
|
|||
|
|
<li><strong>Encapsulation:</strong> If you ever switch from Microsoft Graph to Google Drive or an S3 bucket, you only change one file (<code>services/onedrive.ts</code>) instead of hunting down fetch calls in your API routes.</li>
|
|||
|
|
<li><strong>Error Handling:</strong> You can handle OneDrive-specific errors (like 429 Rate Limiting or 401 Expirations) in one place.</li>
|
|||
|
|
<li><strong>Reusability:</strong> You’ll likely need to download files in other parts of the app (e.g., for a "Download" button or a "Thumbnail Generator").</li>
|
|||
|
|
</ul>
|
|||
|
|
<p><strong>Recommended structure for <code>services/onedrive.ts</code>:</strong></p>
|
|||
|
|
<pre class="hljs"><code><div><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getOneDriveFileBuffer</span>(<span class="hljs-params">oneDriveId: <span class="hljs-built_in">string</span>, token: <span class="hljs-built_in">string</span></span>): <span class="hljs-title">Promise</span><<span class="hljs-title">Buffer</span>> </span>{
|
|||
|
|
<span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(
|
|||
|
|
<span class="hljs-string">`https://graph.microsoft.com/v1.0/me/drive/items/<span class="hljs-subst">${oneDriveId}</span>/content`</span>,
|
|||
|
|
{
|
|||
|
|
headers: {
|
|||
|
|
<span class="hljs-string">'Authorization'</span>: <span class="hljs-string">`Bearer <span class="hljs-subst">${token}</span>`</span>,
|
|||
|
|
<span class="hljs-string">'Accept'</span>: <span class="hljs-string">'*/*'</span>
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
<span class="hljs-keyword">if</span> (!response.ok) {
|
|||
|
|
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`OneDrive download failed: <span class="hljs-subst">${response.statusText}</span>`</span>);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
<span class="hljs-keyword">const</span> arrayBuffer = <span class="hljs-keyword">await</span> response.arrayBuffer();
|
|||
|
|
<span class="hljs-keyword">return</span> Buffer.from(arrayBuffer);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
</div></code></pre>
|
|||
|
|
<hr>
|
|||
|
|
<h3 id="2-where-to-put-getmetadatafromjson">2. Where to put <code>getMetadataFromJson</code>?</h3>
|
|||
|
|
<p><strong><code>lib/metadata-extractor.ts</code> is the perfect spot.</strong></p>
|
|||
|
|
<p>In the Next.js ecosystem, the <code>lib/</code> directory is the standard convention for "Internal Libraries" or "Utilities"—code that is purely functional, doesn't depend on React, and performs a specific technical task (like parsing a buffer).</p>
|
|||
|
|
<p><strong>Why this is the right move:</strong></p>
|
|||
|
|
<ul>
|
|||
|
|
<li><strong>Testability:</strong> Since this is a "pure" function (it takes a buffer, returns an object), it is incredibly easy to write unit tests for. You can pass it a sample buffer and verify the JSON output.</li>
|
|||
|
|
<li><strong>Separation of Concerns:</strong> Your API route/service shouldn't need to know <em>how</em> to read EXIF bits; it should just ask the "extractor" for the results.</li>
|
|||
|
|
</ul>
|
|||
|
|
<p><strong>Structure for <code>lib/metadata-extractor.ts</code>:</strong></p>
|
|||
|
|
<pre class="hljs"><code><div><span class="hljs-keyword">import</span> exifReader <span class="hljs-keyword">from</span> <span class="hljs-string">'exif-reader'</span>;
|
|||
|
|
|
|||
|
|
<span class="hljs-comment">// Define an interface for your metadata to keep your Json field typed</span>
|
|||
|
|
<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> FileMetadata {
|
|||
|
|
deviceModel?: <span class="hljs-built_in">string</span>;
|
|||
|
|
capturedAt?: <span class="hljs-built_in">Date</span>;
|
|||
|
|
lat?: <span class="hljs-built_in">number</span>;
|
|||
|
|
lng?: <span class="hljs-built_in">number</span>;
|
|||
|
|
[key: <span class="hljs-built_in">string</span>]: <span class="hljs-built_in">any</span>; <span class="hljs-comment">// Allows for other fields</span>
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">extractExifMetadata</span>(<span class="hljs-params">buffer: Buffer</span>): <span class="hljs-title">FileMetadata</span> </span>{
|
|||
|
|
<span class="hljs-comment">// logic goes here...</span>
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
</div></code></pre>
|
|||
|
|
<hr>
|
|||
|
|
<h3 id="%F0%9F%8F%97%EF%B8%8F-how-it-looks-all-together">🏗️ How it looks all together</h3>
|
|||
|
|
<p>When you put these in their proper places, your <strong>API Route</strong> or <strong>Server Action</strong> becomes very readable "glue" code:</p>
|
|||
|
|
<ol>
|
|||
|
|
<li><strong>Get Token</strong> (Auth Service)</li>
|
|||
|
|
<li><strong>Download Buffer</strong> (<code>services/onedrive.ts</code>)</li>
|
|||
|
|
<li><strong>Extract Data</strong> (<code>lib/metadata-extractor.ts</code>)</li>
|
|||
|
|
<li><strong>Save to DB</strong> (Prisma / DAL)</li>
|
|||
|
|
</ol>
|
|||
|
|
<p><strong>Would you like me to help you refine the internal logic of the <code>extractExifMetadata</code> function to ensure it handles the specific "Rational" numbers (fractions) that EXIF often uses for exposure and GPS?</strong></p>
|
|||
|
|
|
|||
|
|
</body>
|
|||
|
|
</html>
|