no idea
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import { mkdir, readdir, rm } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { createRouter } from '../../create-router';
|
||||
import { getUserProjectsDir } from '@@/data-path';
|
||||
|
||||
export const projectsRouter = createRouter();
|
||||
|
||||
type KeyMapping = { file: string; dir?: string };
|
||||
|
||||
function resolveKey(projDir: string, key: string): KeyMapping | null {
|
||||
if (key === 'projects') return { file: join(projDir, 'index.json') };
|
||||
|
||||
const layoutMatch = key.match(/^proj-layout-(.+)$/);
|
||||
if (layoutMatch) {
|
||||
const id = layoutMatch[1]!;
|
||||
const dir = join(projDir, id);
|
||||
return { file: join(dir, 'layout.json'), dir };
|
||||
}
|
||||
|
||||
const terminalsMatch = key.match(/^proj-terminals-(.+)$/);
|
||||
if (terminalsMatch) {
|
||||
const id = terminalsMatch[1]!;
|
||||
const dir = join(projDir, id);
|
||||
return { file: join(dir, 'terminals.json'), dir };
|
||||
}
|
||||
|
||||
const hostTerminalsMatch = key.match(/^proj-host-terminals-(.+)$/);
|
||||
if (hostTerminalsMatch) {
|
||||
const id = hostTerminalsMatch[1]!;
|
||||
const dir = join(projDir, id);
|
||||
return { file: join(dir, 'host-terminals.json'), dir };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function readJsonFile(path: string): Promise<unknown | null> {
|
||||
const file = Bun.file(path);
|
||||
if (await file.exists()) return file.json();
|
||||
return null;
|
||||
}
|
||||
|
||||
async function writeJsonFile(path: string, data: unknown) {
|
||||
await Bun.write(path, JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
async function readProjectDir(dirPath: string, id: string, result: Record<string, unknown>) {
|
||||
const layout = await readJsonFile(join(dirPath, 'layout.json'));
|
||||
if (layout !== null) result[`proj-layout-${id}`] = layout;
|
||||
|
||||
const terminals = await readJsonFile(join(dirPath, 'terminals.json'));
|
||||
if (terminals !== null) result[`proj-terminals-${id}`] = terminals;
|
||||
|
||||
const hostTerminals = await readJsonFile(join(dirPath, 'host-terminals.json'));
|
||||
if (hostTerminals !== null) result[`proj-host-terminals-${id}`] = hostTerminals;
|
||||
}
|
||||
|
||||
async function readAllProjectsState(projDir: string): Promise<Record<string, unknown>> {
|
||||
const result: Record<string, unknown> = {};
|
||||
|
||||
const indexData = await readJsonFile(join(projDir, 'index.json'));
|
||||
if (indexData !== null) result['projects'] = indexData;
|
||||
|
||||
let entries: import('node:fs').Dirent[] = [];
|
||||
try {
|
||||
entries = await readdir(projDir, { withFileTypes: true });
|
||||
} catch {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
await readProjectDir(join(projDir, entry.name), entry.name, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// GET /projects-state
|
||||
projectsRouter.get('/projects-state', async (ctx) => {
|
||||
const email = ctx.get('user').email;
|
||||
const projDir = getUserProjectsDir(email);
|
||||
|
||||
const state = await readAllProjectsState(projDir);
|
||||
return ctx.json(state);
|
||||
});
|
||||
|
||||
// PATCH /projects-state
|
||||
projectsRouter.patch('/projects-state', async (ctx) => {
|
||||
const email = ctx.get('user').email;
|
||||
const body = ctx.get('body') as Record<string, unknown>;
|
||||
const projDir = getUserProjectsDir(email);
|
||||
|
||||
await mkdir(projDir, { recursive: true });
|
||||
|
||||
for (const [key, value] of Object.entries(body)) {
|
||||
// When writing the projects list, resolve cwds and create project dirs
|
||||
if (key === 'projects' && Array.isArray(value)) {
|
||||
for (const project of value) {
|
||||
const projectFilesDir = join(projDir, project.id, 'files');
|
||||
project.cwd = projectFilesDir;
|
||||
await mkdir(projectFilesDir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
const mapping = resolveKey(projDir, key);
|
||||
if (!mapping) continue;
|
||||
|
||||
if (value === null) {
|
||||
try {
|
||||
await rm(mapping.file, { force: true });
|
||||
if (mapping.dir) {
|
||||
const remaining = await readdir(mapping.dir);
|
||||
if (remaining.length === 0) await rm(mapping.dir, { recursive: true, force: true });
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mapping.dir) await mkdir(mapping.dir, { recursive: true });
|
||||
await writeJsonFile(mapping.file, value);
|
||||
}
|
||||
|
||||
const state = await readAllProjectsState(projDir);
|
||||
return ctx.json(state);
|
||||
});
|
||||
@@ -2,11 +2,14 @@ FROM imbios/bun-node:22-slim
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
python3 make g++ zsh git curl wget ca-certificates \
|
||||
fortune-mod cowsay sudo gosu \
|
||||
python3 make gcc g++ zsh git curl wget ca-certificates \
|
||||
sudo gosu locales \
|
||||
zip unzip tree btop net-tools tmux \
|
||||
&& sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen \
|
||||
&& apt-get clean
|
||||
|
||||
ENV LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
|
||||
|
||||
RUN curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.tar.gz \
|
||||
&& tar -C /opt -xzf nvim-linux-x86_64.tar.gz \
|
||||
&& rm nvim-linux-x86_64.tar.gz
|
||||
@@ -36,13 +39,20 @@ RUN curl -fsSL "https://github.com/eza-community/eza/releases/download/v${EZA_VE
|
||||
&& chmod +x /usr/local/bin/eza \
|
||||
&& rm -rf /tmp/eza.tar.gz /tmp/completions /tmp/man
|
||||
|
||||
RUN mkdir -p /home/officer
|
||||
ENV LAZYGIT_VERSION=0.44.1
|
||||
RUN curl -fsSL "https://github.com/jesseduffield/lazygit/releases/download/v${LAZYGIT_VERSION}/lazygit_${LAZYGIT_VERSION}_Linux_x86_64.tar.gz" -o /tmp/lazygit.tar.gz \
|
||||
&& tar -xzf /tmp/lazygit.tar.gz -C /tmp \
|
||||
&& mv /tmp/lazygit /usr/local/bin/lazygit \
|
||||
&& chmod +x /usr/local/bin/lazygit \
|
||||
&& rm -rf /tmp/lazygit.tar.gz /tmp/LICENSE /tmp/README.md
|
||||
|
||||
|
||||
RUN mkdir -p /home/officer/Documents /home/officer/Downloads /home/officer/Music /home/officer/Videos /home/officer/Pictures /home/officer/Desktop /home/officer/Projects
|
||||
|
||||
WORKDIR /home/officer
|
||||
|
||||
ENV TERMINAL_PTY_PORT=5337
|
||||
|
||||
ENV PATH="/usr/games:${PATH}"
|
||||
|
||||
EXPOSE 5337
|
||||
|
||||
|
||||
@@ -70,4 +70,3 @@ alias setupmines="WINEPREFIX=~/wine/minesweeper winecfg"
|
||||
alias httpserver="python -m http.server 8888"
|
||||
|
||||
clear
|
||||
fortune | cowsay
|
||||
|
||||
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url';
|
||||
import { getHomeDir } from '@@/data-path';
|
||||
import { officerdb, Users } from 'officerdb';
|
||||
|
||||
type WSData = { userId: number; email: string; role: string; sandboxed: boolean; sessionId?: string; cwd?: string };
|
||||
type WSData = { userId: number; email: string; role: string; sandboxed: boolean; sessionId?: string; cwd?: string; cols?: number; rows?: number };
|
||||
type ShellInfo = { command: string; args: string[]; name: string };
|
||||
type BridgeSession = {
|
||||
client: ServerWebSocket<WSData>;
|
||||
@@ -337,6 +337,8 @@ export const terminalWebsocket = {
|
||||
cwd: resolveCwd(process.env.HOME!, ws.data.cwd),
|
||||
homeDir: process.env.HOME,
|
||||
userLabel: email,
|
||||
cols: ws.data.cols,
|
||||
rows: ws.data.rows,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -397,6 +399,8 @@ export const terminalWebsocket = {
|
||||
cwd: resolveCwd(containerHome, ws.data.cwd),
|
||||
homeDir: containerHome,
|
||||
userLabel: email,
|
||||
cols: ws.data.cols,
|
||||
rows: ws.data.rows,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user