Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/cd command #12

Merged
merged 12 commits into from
Nov 2, 2024
5 changes: 0 additions & 5 deletions app.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
<script setup lang="ts">
useProvideUserStore();
useProvideCwdStore();
</script>

<template>
<div class="overflow-hidden bg-black text-white">
<NuxtPage />
Expand Down
Binary file modified bun.lockb
Binary file not shown.
21 changes: 3 additions & 18 deletions composables/cwd.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,14 @@
import path from 'path';
import { createInjectionState } from '@vueuse/core';
import { createGlobalState } from '@vueuse/core';
import { VirtualPath } from '~/lib/path';

const [useProvideCwdStore, _useCwdStore] = createInjectionState(() => {
export const useCwdStore = createGlobalState(() => {
const homeDir = VirtualPath.homeDir('guest');
const cwd = ref(homeDir);
function switchCwd(newDir: string) {
let newPath = cwd.value;
if (path.isAbsolute(newDir)) {
newPath = VirtualPath.createAndCheck(newDir);
} else {
newPath = VirtualPath.create(path.resolve(newPath.toString(), newDir));
}
cwd.value = newPath;
cwd.value = cwd.value.resolve(newDir);
}
return {
cwd,
switchCwd,
};
});

export { useProvideCwdStore };
export function useCwdStore() {
const cwdStore = _useCwdStore();
if (cwdStore == null)
throw new Error('Please call `useProvideCwdStore` on the appropriate parent component');
return cwdStore;
}
14 changes: 3 additions & 11 deletions composables/user.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createInjectionState } from '@vueuse/core';
import { createGlobalState } from '@vueuse/core';

const [useProvideUserStore, _useUserStore] = createInjectionState(() => {
const username = ref('guest');
export const useUserStore = createGlobalState(() => {
const username = ref('');
const userId = ref(null);
const groupId = ref(null);
const createdAt = ref(null);
Expand Down Expand Up @@ -36,11 +36,3 @@ const [useProvideUserStore, _useUserStore] = createInjectionState(() => {
createdAt,
};
});

export { useProvideUserStore };
export function useUserStore() {
const userStore = _useUserStore();
if (userStore == null)
throw new Error('Please call `useUserStore` on the appropriate parent component');
return userStore;
}
25 changes: 25 additions & 0 deletions lib/command/impls/cd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { fileService } from '~/services';
import { formatArg } from '../utils';
import type { CommandFunc } from './types';

export const cd: CommandFunc = async function(...args) {
// discard `cd`
args.shift();
// discard first space
args.shift();

if (args.length !== 1 || !args[0].trim()) {
return [
'Expected an absolute or relative directory name',
];
}

const dirname = formatArg(args[0]);
const res = await fileService.changeDirectory(dirname);
if (res.isOk()) {
return [];
}
return [
res.error()!.message,
];
};
6 changes: 6 additions & 0 deletions lib/command/impls/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ const commandDescriptions: Record<Command, CommandDescription> = {
{ args: ['<command>'] },
],
},
[Command.CD]: {
description: 'Change current working directory',
usages: [
{ args: ['<dirname>'] },
],
},
};

function getDescription(commandName: string): string[] {
Expand Down
3 changes: 2 additions & 1 deletion lib/command/impls/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export type CommandFunc = (...args: string[]) => string[];
export type CommandFunc = (...args: string[]) => Promise<string[]> | string[];

export enum Command {
ECHO = 'echo',
HELP = 'help',
CD = 'cd',
}
4 changes: 4 additions & 0 deletions lib/command/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { help } from './impls/help';
import { Command } from './impls/types';
import { interpretAnsiEscapeColor } from './utils';
import { parse } from '../services/parse';
import { cd } from './impls/cd';

export async function execute(command: string): Promise<ColoredContent> {
const args = parse(command);
Expand All @@ -21,6 +22,9 @@ export async function execute(command: string): Promise<ColoredContent> {
case Command.HELP:
res = help(...args as any);
break;
case Command.CD:
res = await cd(...args as any);
break;
default:
res = echo('echo', ' ', `Unknown command:\\u001b[31m ${args[0]}`);
break;
Expand Down
15 changes: 13 additions & 2 deletions lib/path/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import path from 'path';
import path from 'path-browserify';

export class VirtualPath {
private path: string;
Expand All @@ -16,7 +16,7 @@ export class VirtualPath {
}

static createAndCheck(path: string): VirtualPath {
if (path.match(/^[a-zA-Z 0-9._/]+$/g) !== null) {
if (path.match(/^[a-zA-Z \-0-9._/]+$/g) === null) {
throw new Error('Invalid path pattern');
}
return new VirtualPath(path);
Expand Down Expand Up @@ -51,12 +51,23 @@ export class VirtualPath {

toFormattedString(username: string): string {
const homeDir = VirtualPath.homeDir(username);
if (this.path === '') {
return '/';
}
if (this.path.startsWith(homeDir.path)) {
return '~' + this.path.slice(homeDir.path.length, this.path.length);
}
return this.path;
}

resolve (newDir: string): VirtualPath {
if (this.isRoot() && (['.', '..'].includes(newDir))) return this;
if (path.isAbsolute(newDir)) {
return VirtualPath.create(newDir);
}
return VirtualPath.create(path.resolve(this.path, newDir));
}

static homeDir(username: string): VirtualPath {
return VirtualPath.create(`/home/${username}`);
}
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"lodash-es": "^4.17.21",
"lucide-vue-next": "^0.447.0",
"nuxt": "^3.12.4",
"path-browserify": "^1.0.1",
"pg": "^8.12.0",
"vue": "latest",
"zapatos": "^6.4.2"
Expand All @@ -34,6 +35,7 @@
"@types/folder-hash": "^4.0.4",
"@types/jsonwebtoken": "^9.0.6",
"@types/lodash-es": "^4.17.12",
"@types/path-browserify": "^1.0.3",
"@types/pg": "^8.11.8",
"autoprefixer": "^10.4.20",
"dbmate": "^2.21.0",
Expand Down
2 changes: 1 addition & 1 deletion server/api/auth/login.post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default defineEventHandler(async (event) => {
if (hashedPassword === null || (password && bcrypt.compareSync(password, hashedPassword.trim()))) {
const { JWT_SECRET } = useRuntimeConfig();
const token = jwt.sign({ username: name, userid: id, groupid: group_id }, JWT_SECRET, { expiresIn: '24h' });
setHeader(event, 'Set-Cookie', `jwt=${token}; HttpOnly; SameSite=Strict${isProduction ? '' : '; Secure'}`);
setHeader(event, 'Set-Cookie', `jwt=${token}; HttpOnly; Path=/; SameSite=Strict${!isProduction ? '' : '; Secure'}`);
return { ok: { message: 'Login successfully' } };
}

Expand Down
4 changes: 2 additions & 2 deletions server/api/files/index.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export default defineEventHandler(async (event) => {
return { error: { code: FileMetaGetErrorCode.NOT_ENOUGH_PRIVILEGE, message: 'Should be logged in as a user with enough privilege' } };
}

const { permission_bits, owner_id, group_id } = await db.selectExactlyOne('files', { name: filepath.toString() }).run(dbPool);
const { permission_bits, owner_id, group_id, file_type } = await db.selectExactlyOne('files', { name: filepath.toString() }).run(dbPool);

return { ok: { message: 'Fetch file information successfully', data: { permission: permission_bits, ownerId: owner_id, groupId: group_id, fileName: filepath.toString() } } };
return { ok: { message: 'Fetch file information successfully', data: { permission: permission_bits, ownerId: owner_id, groupId: group_id, fileName: filepath.toString(), fileType: file_type } } };
} catch {
return { error: { code: FileMetaGetErrorCode.FILE_NOT_FOUND, message: 'File not found' } };
}
Expand Down
4 changes: 2 additions & 2 deletions server/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ export interface Accessor {
}

export function canAccess (accessor: Accessor, file: TargetFilePermission, accessType: AccessType): boolean {
const accessBitIndex = accessType === AccessType.READ ? 0 : accessType === AccessType.WRITE ? 1 : 2;
const accessBitIndex = accessType === AccessType.READ ? 2 : accessType === AccessType.WRITE ? 1 : 0;
const userKindIndex = file.ownerId === accessor.userId ? 2 : file.groupId === accessor.groupId ? 1 : 0;
const bitIndex = userKindIndex * 3 + accessBitIndex;
const bitIndex = 11 - (userKindIndex * 3 + accessBitIndex);
return file.permissionBits[bitIndex] === '1';
}
23 changes: 22 additions & 1 deletion services/files.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Diagnostic, Result } from './types';
import { Err, Ok, type Diagnostic, type Result } from './types';

export enum UserKind {
OWNER = 'owner',
Expand Down Expand Up @@ -46,6 +46,27 @@ export const fileService = {
async createFile (filename: string): Promise<Result<null, Diagnostic>> {
},
async changeDirectory (filename: string): Promise<Result<null, Diagnostic>> {
try {
const { cwd, switchCwd } = useCwdStore();
const meta = await $fetch('/api/files', {
method: 'get',
query: {
name: cwd.value.resolve(filename).toString(),
},
credentials: 'include',
});
if (meta.error) {
return new Err({ code: meta.error.code, message: meta.error.message });
}
const { ok: { data } } = meta;
if (data.fileType !== 'directory') {
return new Err({ code: 1, message: 'Expected a directory' });
}
switchCwd(filename);
return new Ok(null);
} catch {
return new Err({ code: 500, message: 'Network connection error' });
}
},
async moveFile (filename: string, destination: string): Promise<Result<null, Diagnostic>> {
},
Expand Down
8 changes: 8 additions & 0 deletions services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export class Ok<T> {
map<R> (callback: (_: T) => R): Ok<R> {
return new Ok(callback(this.data));
}

error () {
throw new Error('Ok does not contain error messages');
}
}

export class Err<E> {
Expand All @@ -38,6 +42,10 @@ export class Err<E> {
map (): Err<E> {
return this;
}

error (): E {
return this.err;
}
}

export interface Diagnostic {
Expand Down
Loading