aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSkitty1238 <157652284+Skitty1238@users.noreply.github.com>2025-06-04 13:59:34 -0400
committerSkitty1238 <157652284+Skitty1238@users.noreply.github.com>2025-06-04 13:59:34 -0400
commit645df1d00f953524c6da22103d26c38ae4331cd6 (patch)
tree85fc3d4d3f0df579b5c53387d579e46b24e5c543 /src
parent5a998955235d141d2488c1c2deef50a3d6cf65c7 (diff)
partial google calendar task integration + fix with dark mode colors for task nodes so that text is still visible
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/GoogleAuthenticationManager.tsx35
-rw-r--r--src/client/views/MainViewModal.tsx2
-rw-r--r--src/client/views/nodes/TaskBox.scss11
-rw-r--r--src/client/views/nodes/TaskBox.tsx69
-rw-r--r--src/server/ApiManagers/FireflyManager.ts11
-rw-r--r--src/server/ApiManagers/GeneralGoogleManager.ts73
-rw-r--r--src/server/apis/google/GoogleApiServerUtils.ts39
-rw-r--r--src/server/apis/google/google_project_credentials.json6
-rw-r--r--src/server/authentication/DashUserModel.ts6
9 files changed, 153 insertions, 99 deletions
diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index 46581397d..1b1d6f734 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -1,4 +1,4 @@
-import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Opt } from '../../fields/Doc';
@@ -6,12 +6,13 @@ import { Networking } from '../Network';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { MainViewModal } from '../views/MainViewModal';
import './GoogleAuthenticationManager.scss';
+import { ObservableReactComponent } from '../views/ObservableReactComponent';
const AuthenticationUrl = 'https://accounts.google.com/o/oauth2/v2/auth';
const prompt = 'Paste authorization code here...';
@observer
-export class GoogleAuthenticationManager extends React.Component<object> {
+export class GoogleAuthenticationManager extends ObservableReactComponent<object> {
// eslint-disable-next-line no-use-before-define
public static Instance: GoogleAuthenticationManager;
private authenticationLink: Opt<string> = undefined;
@@ -23,6 +24,12 @@ export class GoogleAuthenticationManager extends React.Component<object> {
@observable private credentials: { user_info: { name: string; picture: string }; access_token: string } | undefined = undefined;
private disposer: Opt<IReactionDisposer>;
+ constructor(props: object) {
+ super(props);
+ makeObservable(this);
+ GoogleAuthenticationManager.Instance = this;
+ }
+
private set isOpen(value: boolean) {
runInAction(() => (this.openState = value));
}
@@ -49,14 +56,15 @@ export class GoogleAuthenticationManager extends React.Component<object> {
() => this.authenticationCode,
async authenticationCode => {
if (authenticationCode && /\d{1}\/[\w-]{55}/.test(authenticationCode)) {
+ resolve(authenticationCode);
this.disposer?.();
- const response2 = await Networking.PostToServer('/writeGoogleAccessToken', { authenticationCode });
- runInAction(() => {
- this.success = true;
- this.credentials = response2 as { user_info: { name: string; picture: string }; access_token: string };
- });
+ // const response2 = await Networking.PostToServer('/writeGoogleAccessToken', { authenticationCode });
+ // runInAction(() => {
+ // this.success = true;
+ // this.credentials = response2 as { user_info: { name: string; picture: string }; access_token: string };
+ // });
+ // resolve((response2 as { access_token: string }).access_token);
this.resetState();
- resolve((response2 as { access_token: string }).access_token);
}
}
);
@@ -109,11 +117,6 @@ export class GoogleAuthenticationManager extends React.Component<object> {
}
});
- constructor(props: object) {
- super(props);
- GoogleAuthenticationManager.Instance = this;
- }
-
private get renderPrompt() {
return (
<div className={'authorize-container'}>
@@ -153,7 +156,11 @@ export class GoogleAuthenticationManager extends React.Component<object> {
}
render() {
- return <MainViewModal isDisplayed={this.openState} interactive={true} contents={this.renderPrompt} dialogueBoxStyle={this.dialogueBoxStyle} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={action(() => (this.isOpen = false))} />;
+ return (
+ <div className="hell">
+ <MainViewModal isDisplayed={this.openState} interactive={true} contents={this.renderPrompt} dialogueBoxStyle={this.dialogueBoxStyle} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={action(() => (this.isOpen = false))} />
+ </div>
+ );
}
}
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index b05292c47..d7640dc72 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -41,7 +41,7 @@ export class MainViewModal extends React.Component<MainViewOverlayProps> {
className="overlay"
onClick={this.props?.closeOnExternalClick}
style={{
- backgroundColor: isDark(SnappingManager.userColor) ? '#DFDFDF30' : '#32323230',
+ backgroundColor: isDark(SnappingManager.userColor ?? '') ? '#DFDFDF30' : '#32323230',
...(p.overlayStyle || {}),
}}
/>
diff --git a/src/client/views/nodes/TaskBox.scss b/src/client/views/nodes/TaskBox.scss
index 0fcc2f955..6ef0c6454 100644
--- a/src/client/views/nodes/TaskBox.scss
+++ b/src/client/views/nodes/TaskBox.scss
@@ -6,6 +6,15 @@
width: 100%;
height: 100%;
box-sizing: border-box;
+
+ input,
+ textarea,
+ select,
+ button {
+ background-color: #fff !important;
+ color: #000 !important;
+ border-color: #ccc !important;
+ }
}
.task-manager-title {
@@ -69,4 +78,4 @@ input[type="datetime-local"] {
border: 1px solid #ccc;
border-radius: 6px;
box-sizing: border-box;
-}
+} \ No newline at end of file
diff --git a/src/client/views/nodes/TaskBox.tsx b/src/client/views/nodes/TaskBox.tsx
index 1c7aeeb82..3990356b9 100644
--- a/src/client/views/nodes/TaskBox.tsx
+++ b/src/client/views/nodes/TaskBox.tsx
@@ -270,43 +270,38 @@ export class TaskBox extends React.Component<TaskBoxProps> {
)}
{/** test button */}
- <button
- className="task-manager-google"
- onClick={async () => {
- console.log('GT button clicked');
- const token = await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
- console.log('Got token', token);
-
- try {
- const response = await fetch('/googleTasks/create', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${token}`,
- },
- body: JSON.stringify({
- title: taskTitle || 'Untitled Task',
- notes: taskDesc,
- due: allDay
- ? String(doc.$task_dateRange)?.split('|')[0] + 'T23:59:00Z'
- : (doc.$task_endTime instanceof DateField && doc.$task_endTime.date?.toISOString()) || undefined,
- }),
- });
-
- const result = await response.json();
- console.log("📬 Google Task result:", result);
- alert(result?.id ? 'Task sent to Google Tasks!' : `Failed: ${result?.error?.message || 'Unknown error'}`);
- } catch (err) {
- console.error("Fetch error:", err);
- alert("Fetch failed: ");
- }
- }}
- >
- GT
- </button>
-
-
-
+ <button
+ className="task-manager-google"
+ onClick={async () => {
+ console.log('GT button clicked');
+ const token = await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
+ console.log('Got token', token);
+
+ try {
+ const response = await fetch('/googleTasks/create', {
+ method: 'POST',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify({
+ title: taskTitle || 'Untitled Task',
+ notes: taskDesc,
+ due: allDay ? String(doc.$task_dateRange)?.split('|')[0] + 'T23:59:00Z' : (doc.$task_endTime instanceof DateField && doc.$task_endTime.date?.toISOString()) || undefined,
+ }),
+ });
+
+ const result = await response.text();
+ console.log('Google Task result:', result);
+ alert(result?.id ? 'Task sent to Google Tasks!' : `Failed: ${result?.error?.message || 'Unknown error'}`);
+ } catch (err) {
+ console.error('Fetch error:', err);
+ alert('Fetch failed: ');
+ }
+ }}>
+ GT
+ </button>
</div>
);
}
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
index 07ef271a1..6393a1f74 100644
--- a/src/server/ApiManagers/FireflyManager.ts
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -23,7 +23,16 @@ export default class FireflyManager extends ApiManager {
return undefined;
});
- generateImageFromStructure = (prompt: string = 'a realistic illustration of a cat coding', width: number = 2048, height: number = 2048, structureUrl: string, strength: number = 50, styles: string[], styleUrl: string | undefined, variations: number = 4) =>
+ generateImageFromStructure = (
+ prompt: string = 'a realistic illustration of a cat coding',
+ width: number = 2048,
+ height: number = 2048,
+ structureUrl: string,
+ strength: number = 50,
+ styles: string[],
+ styleUrl: string | undefined,
+ variations: number = 4
+ ) =>
this.getBearerToken().then(response =>
response?.json().then((data: { access_token: string }) =>
//prettier-ignore
diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts
index aa06ca1b3..e8debfc12 100644
--- a/src/server/ApiManagers/GeneralGoogleManager.ts
+++ b/src/server/ApiManagers/GeneralGoogleManager.ts
@@ -1,11 +1,10 @@
import ApiManager, { Registration } from './ApiManager';
-import { Method } from '../RouteManager';
+import { Method, _success } from '../RouteManager';
import { GoogleApiServerUtils } from '../apis/google/GoogleApiServerUtils';
import RouteSubscriber from '../RouteSubscriber';
import { Database } from '../database';
import { google } from 'googleapis';
-
const EndpointHandlerMap = new Map<GoogleApiServerUtils.Action, GoogleApiServerUtils.ApiRouter>([
['create', (api, params) => api.create(params)],
['retrieve', (api, params) => api.get(params)],
@@ -18,9 +17,10 @@ export default class GeneralGoogleManager extends ApiManager {
method: Method.GET,
subscription: '/readGoogleAccessToken',
secureHandler: async ({ user, res }) => {
- const { credentials } = await GoogleApiServerUtils.retrieveCredentials(user.id);
+ const { credentials } = await GoogleApiServerUtils.retrieveCredentials(user);
if (!credentials?.access_token) {
- return res.send(GoogleApiServerUtils.generateAuthenticationUrl());
+ const url = GoogleApiServerUtils.generateAuthenticationUrl();
+ return res.send(url);
}
return res.send(credentials);
},
@@ -49,7 +49,7 @@ export default class GeneralGoogleManager extends ApiManager {
secureHandler: async ({ req, res, user }) => {
const sector: GoogleApiServerUtils.Service = req.params.sector as GoogleApiServerUtils.Service;
const action: GoogleApiServerUtils.Action = req.params.action as GoogleApiServerUtils.Action;
- const endpoint = await GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], user.id);
+ const endpoint = await GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], user);
const handler = EndpointHandlerMap.get(action);
if (endpoint && handler) {
try {
@@ -70,31 +70,48 @@ export default class GeneralGoogleManager extends ApiManager {
method: Method.POST,
subscription: new RouteSubscriber('googleTasks').add('create'),
secureHandler: async ({ req, res, user }) => {
- try {
- const { credentials } = await GoogleApiServerUtils.retrieveCredentials(user.id);
- if (!credentials?.access_token) {
- return res.status(401).send('Google access token not found.');
+ try {
+ const { credentials } = await GoogleApiServerUtils.retrieveCredentials(user.id);
+ const access_token = user.googleToken || credentials?.access_token; // if googleToken expires, we need to renew it.
+
+ if (!access_token) {
+ return res.status(401).send('Google access token not found.');
+ }
+
+ const auth = new google.auth.OAuth2();
+ auth.setCredentials({ access_token: access_token });
+
+ const tasks = google.tasks({ version: 'v1', auth });
+
+ const { title, notes, due } = req.body;
+ const result = await tasks.tasks.insert({
+ tasklist: '@default',
+ requestBody: { title, notes, due },
+ });
+
+ res.status(200).send(result.data);
+ } catch (err) {
+ console.error('Google Tasks error:', err);
+ res.status(500).send('Failed to create task.');
}
-
- const auth = new google.auth.OAuth2();
- auth.setCredentials({ access_token: credentials.access_token });
-
- const tasks = google.tasks({ version: 'v1', auth });
-
- const { title, notes, due } = req.body;
- const result = await tasks.tasks.insert({
- tasklist: '@default',
- requestBody: { title, notes, due },
- });
-
- res.status(200).send(result.data);
- } catch (err) {
- console.error('Google Tasks error:', err);
- res.status(500).send('Failed to create task.');
- }
},
- });
+ });
-
+ register({
+ method: Method.GET,
+ subscription: '/refreshGoogle',
+ secureHandler: async ({ user, req, res }) => {
+ const code = req.query.code as string;
+ _success(res, code);
+ user.googleToken = code;
+ user.save();
+
+ // const response2 = await Networking.PostToServer('/writeGoogleAccessToken', { authenticationCode });
+ // runInAction(() => {
+ // this.success = true;
+ // this.credentials = response2 as { user_info: { name: string; picture: string }; access_token: string };
+ // });
+ },
+ });
}
}
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index 7373df473..75f904331 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -6,6 +6,7 @@ import * as request from 'request-promise';
import { Opt } from '../../../fields/Doc';
import { Database } from '../../database';
import { GoogleCredentialsLoader } from './CredentialsLoader';
+import { DashUserModel } from '../../authentication/DashUserModel';
/**
* Scopes give Google users fine granularity of control
@@ -13,7 +14,7 @@ import { GoogleCredentialsLoader } from './CredentialsLoader';
* This is the somewhat overkill list of what Dash requests
* from the user.
*/
-const scope = ['documents.readonly', 'documents', 'presentations', 'presentations.readonly', 'drive', 'drive.file', 'photoslibrary', 'photoslibrary.appendonly', 'photoslibrary.sharing', 'userinfo.profile'].map(
+const scope = ['tasks', 'documents.readonly', 'documents', 'presentations', 'presentations.readonly', 'drive', 'drive.file', 'photoslibrary', 'photoslibrary.appendonly', 'photoslibrary.sharing', 'userinfo.profile'].map(
relative => `https://www.googleapis.com/auth/${relative}`
);
@@ -118,8 +119,9 @@ export namespace GoogleApiServerUtils {
* @param userId the id of the Dash user making the request to the API
* @returns the relevant 'googleapis' wrapper, if any
*/
- export async function GetEndpoint(sector: string, userId: string): Promise<Endpoint | void> {
- const auth = await retrieveOAuthClient(userId);
+ export async function GetEndpoint(sector: string, user: DashUserModel): Promise<Endpoint | void> {
+ if (!user.googleToken) await retrieveOAuthClient(user);
+ const auth = user.googleToken; // await retrieveOAuthClient(user);
if (!auth) {
return;
}
@@ -145,14 +147,14 @@ export namespace GoogleApiServerUtils {
* npm-installed API wrappers that use authenticated client instances rather than access codes for
* security.
*/
- export async function retrieveOAuthClient(userId: string): Promise<OAuth2Client | void> {
- const { credentials, refreshed } = await retrieveCredentials(userId);
+ export async function retrieveOAuthClient(user: DashUserModel): Promise<OAuth2Client | void> {
+ const { credentials, refreshed } = await retrieveCredentials(user);
if (!credentials) {
return;
}
- let client = authenticationClients.get(userId);
+ let client = authenticationClients.get(user.id);
if (!client) {
- authenticationClients.set(userId, (client = generateClient(credentials)));
+ authenticationClients.set(user.id, (client = generateClient(credentials)));
} else if (refreshed) {
client.setCredentials(credentials);
}
@@ -181,7 +183,16 @@ export namespace GoogleApiServerUtils {
* @returns the newly generated url to the authentication landing page
*/
export function generateAuthenticationUrl(): string {
- return worker.generateAuthUrl({ scope, access_type: 'offline' });
+ const oauth2Client = new google.auth.OAuth2(
+ '838617994486-a28072lirm8uk8cm78t7ic4krp0rgkgv.apps.googleusercontent.com',
+ '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',
+ 'http://localhost:1050/refreshGoogle' // Ensure this matches the redirect URI in Google Cloud Console
+ );
+
+ return oauth2Client.generateAuthUrl({
+ access_type: 'offline',
+ scope: ['https://www.googleapis.com/auth/tasks'],
+ });
}
/**
@@ -267,15 +278,15 @@ export namespace GoogleApiServerUtils {
* @returns the credentials, or undefined if the user has no stored associated credentials,
* and a flag indicating whether or not they were refreshed during retrieval
*/
- export async function retrieveCredentials(userId: string): Promise<{ credentials: Opt<EnrichedCredentials>; refreshed: boolean }> {
- let credentials = await Database.Auxiliary.GoogleAccessToken.Fetch(userId);
+ export async function retrieveCredentials(user: DashUserModel): Promise<{ credentials: Opt<EnrichedCredentials>; refreshed: boolean }> {
+ let credentials = await Database.Auxiliary.GoogleAccessToken.Fetch(user.id);
let refreshed = false;
if (!credentials) {
return { credentials: undefined, refreshed };
}
// check for token expiry
if (credentials.expiry_date! <= new Date().getTime()) {
- credentials = { ...credentials, ...(await refreshAccessToken(credentials, userId)) };
+ credentials = { ...credentials, ...(await refreshAccessToken(credentials, user)) };
refreshed = true;
}
return { credentials, refreshed };
@@ -291,11 +302,11 @@ export namespace GoogleApiServerUtils {
* his/her credentials be refreshed
* @returns the updated credentials
*/
- async function refreshAccessToken(credentials: Credentials, userId: string): Promise<Credentials> {
+ async function refreshAccessToken(credentials: Credentials, user: DashUserModel): Promise<Credentials> {
const headerParameters = { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } };
const { client_id, client_secret } = GoogleCredentialsLoader.ProjectCredentials;
const params = new URLSearchParams({
- refresh_token: credentials.refresh_token!,
+ refresh_token: credentials.refresh_token!, // AARAV use user.googleToken
client_id,
client_secret,
grant_type: 'refresh_token',
@@ -306,7 +317,7 @@ export namespace GoogleApiServerUtils {
});
// expires_in is in seconds, but we're building the new expiry date in milliseconds
const expiry_date = new Date().getTime() + expires_in * 1000;
- await Database.Auxiliary.GoogleAccessToken.Update(userId, access_token, expiry_date);
+ await Database.Auxiliary.GoogleAccessToken.Update(user.id, access_token, expiry_date);
// update the relevant properties
credentials.access_token = access_token;
credentials.expiry_date = expiry_date;
diff --git a/src/server/apis/google/google_project_credentials.json b/src/server/apis/google/google_project_credentials.json
index 955c5a3c1..738e13647 100644
--- a/src/server/apis/google/google_project_credentials.json
+++ b/src/server/apis/google/google_project_credentials.json
@@ -1,11 +1,11 @@
{
"installed": {
- "client_id": "343179513178-ud6tvmh275r2fq93u9eesrnc66t6akh9.apps.googleusercontent.com",
- "project_id": "quickstart-1565056383187",
+ "client_id": "838617994486-a28072lirm8uk8cm78t7ic4krp0rgkgv.apps.googleusercontent.com",
+ "project_id": "gtasks-test-dash",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
- "client_secret": "w8KIFSc0MQpmUYHed4qEzn8b",
+ "client_secret": "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",
"redirect_uris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost"]
}
} \ No newline at end of file
diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts
index debeef60c..4397e2bd4 100644
--- a/src/server/authentication/DashUserModel.ts
+++ b/src/server/authentication/DashUserModel.ts
@@ -9,9 +9,14 @@ export type DashUserModel = mongoose.Document & {
passwordResetToken?: string;
passwordResetExpires?: Date;
+
+ // AARAV ADD
+ googleToken?: string;
+
dropboxRefresh?: string;
dropboxToken?: string;
+
userDocumentId: string;
sharingDocumentId: string;
linkDatabaseId: string;
@@ -40,6 +45,7 @@ const userSchema = new mongoose.Schema(
passwordResetToken: String,
passwordResetExpires: Date,
+ googleToken: String,
dropboxRefresh: String,
dropboxToken: String,
userDocumentId: String, // id that identifies a document which hosts all of a user's account data