Skip to content

Commit

Permalink
Improve git pull usability
Browse files Browse the repository at this point in the history
Minor refactors
  • Loading branch information
ggodlewski committed Aug 29, 2024
1 parent d07d6a7 commit 8327a08
Show file tree
Hide file tree
Showing 15 changed files with 224 additions and 58 deletions.
2 changes: 1 addition & 1 deletion apps/ui/src/components/DriveTools.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import {UiMixin} from './UiMixin.ts';
import ToolButton from './ToolButton.vue';
export default {
name: 'PreviewHeader',
name: 'DriveTools',
mixins: [UtilsMixin, UiMixin],
components: {ToolButton},
props: {
Expand Down
16 changes: 15 additions & 1 deletion apps/ui/src/components/GitFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="card-body">
<slot></slot>
<button v-if="git_remote_url" type="button" class="btn btn-danger" @click="push"><i v-if="active_jobs.length > 0" class="fa-solid fa-rotate fa-spin"></i> Push</button>
<button v-if="git_remote_url" type="button" class="btn btn-secondary" @click="pull"><i v-if="active_jobs.length > 0" class="fa-solid fa-rotate fa-spin"></i> Pull</button>
<button v-if="git_remote_url" type="button" class="btn btn-secondary" @click="reset_pull"><i v-if="active_jobs.length > 0" class="fa-solid fa-rotate fa-spin"></i> Reset and Pull</button>
<button v-if="git_remote_url" type="button" class="btn btn-secondary" @click="fetch"><i v-if="active_jobs.length > 0" class="fa-solid fa-rotate fa-spin"></i> Fetch</button>
<span v-if="!git_remote_url">
No git remote, go to <router-link :to="{ name: 'drive', params: { driveId }, hash: '#git_settings' }">settings</router-link>
Expand Down Expand Up @@ -41,6 +41,20 @@ export default {
});
});
},
async reset_pull(event) {
await disableElement(event, async () => {
await this.authenticatedClient.fetchApi(`/api/git/${this.driveId}/reset_local`, {
method: 'post'
});
await this.authenticatedClient.fetchApi(`/api/git/${this.driveId}/pull`, {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({})
});
});
},
async pull(event) {
await disableElement(event, async () => {
await this.authenticatedClient.fetchApi(`/api/git/${this.driveId}/pull`, {
Expand Down
18 changes: 18 additions & 0 deletions apps/ui/src/components/GitSideBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<div class="dropdown" v-if="slotProps.ctx">
<ul class="dropdown-menu show">
<li><button class="dropdown-item" type="button" @click="removeFile(slotProps.ctx)"><i class="fa-solid fa-trash"></i> Remove</button></li>
<li><button class="dropdown-item" type="button" @click="removeFileCached(slotProps.ctx)"><i class="fa-solid fa-trash"></i> Remove from git</button></li>
</ul>
</div>
</template>
Expand All @@ -37,6 +38,7 @@
<script>
import GitSideBarRow from './GitSideBarRow.vue';
import ContextMenu from './ContextMenu.vue';
import {UtilsMixin} from './UtilsMixin.ts';
export default {
components: {
Expand All @@ -48,6 +50,7 @@ export default {
checked: Object,
selectedPath: String
},
mixins: [ UtilsMixin ],
data() {
return {
tree: [],
Expand Down Expand Up @@ -119,6 +122,21 @@ export default {
await this.FileClientService.removeFile('/' + this.driveId + (path.startsWith('/') ? path : '/' + path));
this.$refs.contextMenu.close();
},
async removeFileCached(file) {
if (!window.confirm('Are you sure?')) {
this.$refs.contextMenu.close();
return;
}
const filePath = file.path;
await this.authenticatedClient.fetchApi(`/api/git/${this.driveId}/remove_cached`, {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({ filePath })
});
this.$refs.contextMenu.close();
},
showContextMenu(event, ctx) {
this.$refs.contextMenu.open(event, ctx);
},
Expand Down
4 changes: 2 additions & 2 deletions apps/ui/src/pages/FolderView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@
</template>
</GitSettings>

<UserSettings v-if="!contentDir && !(activeTab === 'drive_config' || activeTab === 'drive_config_git')" :activeTab="activeTab">
<UserSettings v-if="!(activeTab === 'drive_config' || activeTab === 'drive_config_git')" :activeTab="activeTab">
<template v-slot:header>
<div class="card-header alert-danger">
<div class="card-header alert-danger" v-if="!contentDir">
Content subdirectory must be set and start with /
</div>
</template>
Expand Down
2 changes: 1 addition & 1 deletion src/containers/action/ActionRunnerContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ export class ActionRunnerContainer extends Container {

private payloadToEnv() {
const additionalEnv = {};
additionalEnv['REMOTE_BRANCH'] = this.userConfigService.config?.remote_branch || 'master';
additionalEnv['REMOTE_BRANCH'] = this.userConfigService.config?.remote_branch || 'main';

if (this.params.payload && this.params.payload.startsWith('{')) {
try {
Expand Down
3 changes: 3 additions & 0 deletions src/containers/changes/WatchChangesContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ export class WatchChangesContainer extends Container {
return;
}
this.intervals[driveId] = setInterval(async () => {
if (!this.auth) {
return;
}
if (this.working[driveId]) {
return;
}
Expand Down
32 changes: 20 additions & 12 deletions src/containers/folder_registry/FolderRegistryContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,29 @@ export class FolderRegistryContainer extends Container {
const oldDrives = Object.values(await this.getFolders());

const apiContainer: GoogleApiContainer = <GoogleApiContainer>this.engine.getContainer('google_api');
const drives = await apiContainer.listDrives();

for (const newDrive of drives) {
if (!oldDrives.find(oldDrive => oldDrive.id === newDrive.id)) {
try {
await this.registerFolder(newDrive.id);
} catch (err) {
this.logger.error(err.stack ? err.stack : err.message);
try {
const drives = await apiContainer.listDrives();

for (const newDrive of drives) {
if (!oldDrives.find(oldDrive => oldDrive.id === newDrive.id)) {
try {
await this.registerFolder(newDrive.id);
} catch (err) {
this.logger.error(err.stack ? err.stack : err.message);
}
}
}
}
for (const oldDrive of oldDrives) {
if (!drives.find(newDrive => newDrive.id === oldDrive.id)) {
await this.unregisterFolder(oldDrive.id);
for (const oldDrive of oldDrives) {
if (!drives.find(newDrive => newDrive.id === oldDrive.id)) {
await this.unregisterFolder(oldDrive.id);
}
}
} catch (err) {
if (401 === err?.status) {
this.logger.warn('Not authenticated to Google API. Skipping drives refresh.');
return;
}
throw err;
}
}

Expand Down
17 changes: 17 additions & 0 deletions src/containers/google_api/GoogleApiContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {GoogleFile} from '../../model/GoogleFile.ts';
import {GoogleAuth, HasAccessToken, UserAuthClient, ServiceAuthClient, getCliCode} from '../../google/AuthClient.ts';

import {fileURLToPath} from 'url';
import {AuthError} from '../server/auth.ts';

const __filename = fileURLToPath(import.meta.url);

Expand Down Expand Up @@ -70,23 +71,39 @@ export class GoogleApiContainer extends Container {
}

async listDrives(): Promise<Drive[]> {
if (!this.auth) {
throw new AuthError('Not authenticated', 401);
}

const googleDriveService = new GoogleDriveService(this.logger, this.quotaLimiter);
const accessToken = await this.auth.getAccessToken();
return await googleDriveService.listDrives(accessToken);
}

async getDrive(driveId: FileId): Promise<Drive> {
if (!this.auth) {
throw new AuthError('Not authenticated', 401);
}

const googleDriveService = new GoogleDriveService(this.logger, this.quotaLimiter);
const accessToken = await this.auth.getAccessToken();
return await googleDriveService.getDrive(accessToken, driveId);
}

async getFolder(fileId: FileId): Promise<GoogleFile> {
if (!this.auth) {
throw new AuthError('Not authenticated', 401);
}

const googleDriveService = new GoogleDriveService(this.logger, this.quotaLimiter);
return await googleDriveService.getFile(this.auth, fileId);
}

async shareDrive(driveId: string, shareEmail: string): Promise<Permission> {
if (!this.auth) {
throw new AuthError('Not authenticated', 401);
}

const googleDriveService = new GoogleDriveService(this.logger, this.quotaLimiter);
const accessToken = await this.auth.getAccessToken();
return await googleDriveService.shareDrive(accessToken, driveId, shareEmail);
Expand Down
2 changes: 1 addition & 1 deletion src/containers/google_folder/UserConfigService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class UserConfig {
}

const DEFAULT_CONFIG: UserConfig = {
remote_branch: 'master',
remote_branch: 'main',
hugo_theme: {
id: 'ananke',
name: 'Anake',
Expand Down
26 changes: 15 additions & 11 deletions src/containers/server/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,24 +61,24 @@ interface JwtDecryptedPayload extends GoogleUser {
driveId: string;
}

export function signToken(payload: JwtDecryptedPayload): string {
const expiresIn = 365 * 24 * 3600; // process.env.JWT_ACCESS_TOKEN_EXPIRATION_TIME ||
function signToken(payload: JwtDecryptedPayload, jwtSecret: string): string {
const expiresIn = 365 * 24 * 3600;

const encrypted: JwtEncryptedPayload = {
sub: payload.id,
name: payload.name,
email: payload.email,
gat: encrypt(payload.google_access_token, process.env.JWT_SECRET),
grt: payload.google_refresh_token ? encrypt(payload.google_refresh_token, process.env.JWT_SECRET) : undefined,
gat: encrypt(payload.google_access_token, jwtSecret),
grt: payload.google_refresh_token ? encrypt(payload.google_refresh_token, jwtSecret) : undefined,
ged: payload.google_expiry_date,
driveId: payload.driveId
};

return jsonwebtoken.sign(encrypted, process.env.JWT_SECRET, { expiresIn });
return jsonwebtoken.sign(encrypted, jwtSecret, { expiresIn });
}

export function verifyToken(accessCookie: string): JwtDecryptedPayload {
const encrypted: JwtEncryptedPayload = <JwtEncryptedPayload>jsonwebtoken.verify(accessCookie, process.env.JWT_SECRET);
function verifyToken(accessCookie: string, jwtSecret: string): JwtDecryptedPayload {
const encrypted: JwtEncryptedPayload = <JwtEncryptedPayload>jsonwebtoken.verify(accessCookie, jwtSecret);

return {
id: encrypted.sub,
Expand Down Expand Up @@ -240,14 +240,16 @@ export async function getAuth(req: Request, res: Response, next) {
const googleDriveService = new GoogleDriveService(this.logger, null);
const googleUser: GoogleUser = await authClient.getUser(await authClient.getAccessToken());

const jwtSecret = process.env.JWT_SECRET;

if (driveId) {
const drive = await googleDriveService.getDrive(await authClient.getAccessToken(), driveId);
if (drive.id) {
const accessToken = signToken({
...googleUser,
...await authClient.getAuthData(),
driveId: driveId
});
}, jwtSecret);
setAccessCookie(res, accessToken);
res.redirect(redirectTo || '/');
return;
Expand All @@ -257,7 +259,7 @@ export async function getAuth(req: Request, res: Response, next) {
...googleUser,
...await authClient.getAuthData(),
driveId: driveId
});
}, jwtSecret);
setAccessCookie(res, accessToken);
res.redirect(redirectTo || '/drive');
return;
Expand Down Expand Up @@ -289,8 +291,10 @@ async function decodeAuthenticateInfo(req, res, next) {
return;
}

const jwtSecret = process.env.JWT_SECRET;

try {
const decoded = verifyToken(req.cookies.accessToken);
const decoded = verifyToken(req.cookies.accessToken, jwtSecret);
if (!decoded.id) {
return next(redirError(req, 'No jwt.sub'));
}
Expand Down Expand Up @@ -319,7 +323,7 @@ async function decodeAuthenticateInfo(req, res, next) {
...decoded,
...await authClient.getAuthData(),
driveId: driveId
});
}, jwtSecret);
setAccessCookie(res, accessToken);
}

Expand Down
2 changes: 1 addition & 1 deletion src/containers/server/routes/ConfigController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export class ConfigController extends Controller {
await userConfigService.load();

if (body.config?.remote_branch) {
userConfigService.config.remote_branch = body.config?.remote_branch || 'master';
userConfigService.config.remote_branch = body.config?.remote_branch || 'main';
}
if (body.config?.hugo_theme) {
userConfigService.config.hugo_theme = body.config?.hugo_theme;
Expand Down
22 changes: 22 additions & 0 deletions src/containers/server/routes/GitController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ interface CmdPost {
cmd: string;
}

interface RemovePath {
filePath: string;
}

export default class GitController extends Controller {

constructor(subPath: string, private readonly filesService: FileContentService,
Expand Down Expand Up @@ -166,4 +170,22 @@ export default class GitController extends Controller {
}
}

@RoutePost('/:driveId/remove_cached')
async removeCached(@RouteParamPath('driveId') driveId: string, @RouteParamBody() body: RemovePath) {
try {
const transformedFileSystem = await this.filesService.getSubFileService(driveId + '_transform', '');
const gitScanner = new GitScanner(this.logger, transformedFileSystem.getRealPath(), 'wikigdrive@wikigdrive.com');
await gitScanner.initialize();
await gitScanner.removeCached(body.filePath);

return {};
} catch (err) {
this.logger.error(err.stack ? err.stack : err.message);
if (err.message.indexOf('Failed to retrieve list of SSH authentication methods') > -1) {
return { error: 'Failed to authenticate' };
}
throw err;
}
}

}
Loading

0 comments on commit 8327a08

Please sign in to comment.