mirror of
https://github.com/HeyPuter/puter.git
synced 2026-11-07 06:23:05 +00:00
* perf(workers): skip hot-reload lookups for files no worker is bound to
The fs.write.file / fs.remove.node / fs.move.node listeners listed every
workers.puter.* row of the file's owner on every event, for every user.
The event's FSEntry already carries the worker rows bound to it, so a file
with none returns before any I/O; a bound file reads just its own rows,
batched, from the primary.
* perf(apps): read the uid alias only after a direct lookup misses
Every AppDriver uid lookup fired an uncached system-KV GetItem for the
merge alias in parallel with the direct read, even when the direct read
hit. The alias is now read only on a miss.
* refactor(apps): one app view with the launch gates, built by AppService
AppDriver's serializer and its private-app / hosted-backing gates move to
AppService.views, built on a pure toAppView that is the one place
index_url is withheld. AppDriver, the subdomains driver's embedded apps and
the GUI shell view all use it. Visibility of protected apps
(filterReadable) moves with it.
Subdomain embeds (associated_app, app_owner) used to return index_url with
no gate; they now withhold it from a viewer who may not open a private app
or whose hosted backing is gone, and carry privateAccess, godmode and
feedback_enabled like a direct app read.
* refactor(apps): one toAppSummary; batch the recommended and taskbar lookups
SuggestedApps, RecommendedApps, /get-launch-apps and the taskbar each built
their own launch summary; they now share toAppSummary (the extra
feedback_enabled / external flags appear on the lists that lacked them).
getRecommendedApps awaited getByName once per recommended app (29) and
getTaskbarItems did the same per taskbar entry on every whoami; both now
make one batched call per key through the new AppStore.getByNames, which
keeps getByName's case-insensitive and old-name matching. The taskbar's
raw UPDATE of the user row goes through UserStore.update, and
/get-launch-apps checks hosted backings in one batch and reaches the
stores without casts.
* refactor(apps): app routes read through AppService; /delete-site calls the subdomains driver
AppController used the global driversContainers through a getter whose
boot-order comment no longer held; it uses the injected drivers and
AppService. GET /apps/:name looked each name up, read it again through the
driver and resolved private access a second time, emitting
app.privateAccess.resolveLaunch twice per private app; it now batches the
names and resolves once. POST /query/app did a lookup, a driver read and a
raw app_filetype_association SELECT per selector (up to 200, serially); it
now makes one batched lookup per key, one grant check and one filetype read.
The driver's read shape is a pure toAppReadView both use.
HostingController's /delete-site re-implemented the subdomains driver's
delete (protected check, delete, subdomain.delete event); it keeps its
owner-scoped 404 and calls the driver. Hosting and Desktop controllers drop
constructors that discarded the drivers argument, and trivial getters.
* refactor(drivers): one per-method policy validator and resolver; drop @Driver
Four policy blocks (rateLimit, concurrent, requireSubscription,
requireReputation) share the { default, methods } shape but each had its
own validator and resolver. They become validatePerMethod(value, label,
policy) and resolvePerMethod(cfg, method); error messages are unchanged.
The @Driver class decorator and the prototype keys it wrote had no users
outside its own tests; drivers declare their fields, and resolveDriverMeta
reads only those. DriverController takes the driver name from the meta it
already resolved instead of probing the prototype.
resolveDriverMethodRateLimit/Concurrent stay as deprecated aliases for the
email extension.
* refactor(drivers): shared actor guards and crud-q concurrency policy
The apps, subdomains, workers and notifications drivers each carried a
private #requireActor, two carried the same #requireVerified, and apps and
subdomains the same owner/app_owner write predicate. These become
requireContextActor, requireContextUserActor, assertActorEmailVerified and
actorOwnsRow in core/actor.ts; each driver keeps its own error for the
cases that differed (system actors still read apps; notifications still
answer 403 for an actor without a user). The identical crud-q concurrency
block is one CRUD_CONCURRENT constant.
The metering, installedApps and appTelemetry extensions re-checked an actor
their routes already require; those checks and the tests for them go.
* refactor(extensions): store methods instead of raw SQL in OSS extensions
installedApps and appTelemetry queried user_to_app_permissions directly
and metering read apps by name with its own SELECT. PermissionStore gains
listAppsGrantedByUser, listAppAuthenticatedUsers (with the email-share
check folded in) and countAppUsers; metering uses AppStore.getByName.
* refactor(server): one layer builder and one lifecycle hook runner
PuterServer built each of its five layers with its own copy of the same
loop (built-ins, then extension additions, into the layer and its export
container), and ran each lifecycle hook with five near-identical loops per
hook. Both are now one helper each: #instantiateLayer and
#runHook(order, hook). Construction order, the args each layer receives,
and hook order (start and prepare bottom-up, shutdown top-down) are
unchanged.
* fix(workers): tear down a deleted source's worker after its FK nulls root_dir_id
163 lines
7.5 KiB
TypeScript
163 lines
7.5 KiB
TypeScript
/*
|
|
* Copyright (C) 2024-present Puter Technologies Inc.
|
|
*
|
|
* This file is part of Puter.
|
|
*
|
|
* Puter is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published
|
|
* by the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import { AppOriginBlocklistService } from './abuse/AppOriginBlocklistService';
|
|
import { ACLService } from './acl/ACLService';
|
|
import { AppIconService } from './appIcon/AppIconService';
|
|
import { AppPermissionService } from './apps/AppPermissionService';
|
|
import { AppService } from './apps/AppService';
|
|
import { RecommendedAppsService } from './apps/RecommendedAppsService';
|
|
import { SuggestedAppsService } from './apps/SuggestedAppsService';
|
|
import { AuthService } from './auth/AuthService';
|
|
import { OIDCService } from './auth/OIDCService';
|
|
import { TokenService } from './auth/TokenService';
|
|
import { BroadcastService } from './broadcast/BroadcastService';
|
|
import { CacheReplicationService } from './cache/CacheReplicationService';
|
|
import { EventForwardService } from './events/EventForwardService';
|
|
import { EventsService } from './events/EventsService';
|
|
import { AppFeedbackService } from './feedback/AppFeedbackService';
|
|
import { FSService } from './fs/FSService';
|
|
import { ServerHealthService } from './health/ServerHealthService';
|
|
import { PuterHomepageService } from './homepage/PuterHomepageService';
|
|
import { LocalWorkerService } from './localworker/LocalWorkerService';
|
|
import { MeteringService } from './metering/MeteringService';
|
|
import { NotificationService } from './notification/NotificationService';
|
|
import { PermissionService } from './permission/PermissionService';
|
|
import { ProfileService } from './profile/ProfileService';
|
|
import { DefaultUserService } from './selfhosted/DefaultUserService';
|
|
import { ShareNotificationService } from './share/ShareNotificationService';
|
|
import { ShareService } from './share/ShareService';
|
|
import { TeamService } from './team/TeamService';
|
|
import { SocketService } from './socket/SocketService';
|
|
import { SubdomainPermissionService } from './subdomain/SubdomainPermissionService';
|
|
import type { IPuterServiceRegistry } from './types';
|
|
import { SignupService } from './user/SignupService';
|
|
import { UserAccountService } from './user/UserAccountService';
|
|
|
|
/**
|
|
* Populate `IPuterServiceInstances` (declared in `./types`) with the concrete
|
|
* types of built-in services. Done via declaration merging instead of
|
|
* `LayerInstances<typeof puterServices>` because every concrete service extends
|
|
* `PuterService`, whose `protected services` field references this type — a
|
|
* direct `typeof puterServices` lookup would self-cycle.
|
|
*/
|
|
declare module './types' {
|
|
interface IPuterServiceInstances {
|
|
metering: MeteringService;
|
|
appOriginBlocklist: AppOriginBlocklistService;
|
|
permission: PermissionService;
|
|
acl: ACLService;
|
|
share: ShareService;
|
|
shareNotification: ShareNotificationService;
|
|
profile: ProfileService;
|
|
token: TokenService;
|
|
auth: AuthService;
|
|
fs: FSService;
|
|
appPermission: AppPermissionService;
|
|
subdomainPermission: SubdomainPermissionService;
|
|
recommendedApps: RecommendedAppsService;
|
|
suggestedApps: SuggestedAppsService;
|
|
app: AppService;
|
|
socket: SocketService;
|
|
events: EventsService;
|
|
eventForward: EventForwardService;
|
|
notification: NotificationService;
|
|
appFeedback: AppFeedbackService;
|
|
broadcast: BroadcastService;
|
|
cacheReplication: CacheReplicationService;
|
|
oidc: OIDCService;
|
|
appIcon: AppIconService;
|
|
defaultUser: DefaultUserService;
|
|
homepage: PuterHomepageService;
|
|
health: ServerHealthService;
|
|
userAccount: UserAccountService;
|
|
team: TeamService;
|
|
signup: SignupService;
|
|
}
|
|
}
|
|
|
|
// Ordering matters: services declared later see earlier ones as peers.
|
|
// ACLService depends on PermissionService (for scan + grant/revoke), so
|
|
// PermissionService must be constructed first.
|
|
// AuthService depends on TokenService (JWT verify).
|
|
// FSService constructs its own internal repo + S3 provider in onServerStart.
|
|
// SocketService depends on AuthService (for handshake auth).
|
|
// NotificationService depends on notification store (for DB) + event client (for socket push).
|
|
// BroadcastService is independent — only needs the event client.
|
|
export const puterServices = {
|
|
metering: MeteringService,
|
|
// Declared before `auth` so AuthService sees it as a prior peer — it
|
|
// queries the blocklist on app-token acquisition and per-request app
|
|
// token validation.
|
|
appOriginBlocklist: AppOriginBlocklistService,
|
|
permission: PermissionService,
|
|
acl: ACLService,
|
|
token: TokenService,
|
|
auth: AuthService,
|
|
fs: FSService,
|
|
// Needs acl (setUserUser), permission (canManagePermission) and fs
|
|
// (ancestor chains), so it follows all three.
|
|
share: ShareService,
|
|
// Delivery only; it reaches `notification` at call time, so its position
|
|
// relative to that service does not matter.
|
|
shareNotification: ShareNotificationService,
|
|
// Reads and writes profile files through `fs` and asks `metering` about
|
|
// the owner's plan, so it follows both.
|
|
profile: ProfileService,
|
|
// Declared after `fs` — account teardown tears the user's filesystem down
|
|
// first.
|
|
userAccount: UserAccountService,
|
|
// Leaf: team + user stores only.
|
|
team: TeamService,
|
|
// Reaches `team` and `socket` at call time only.
|
|
signup: SignupService,
|
|
// AppPermissionService + SubdomainPermissionService register permission
|
|
// rewriters/implicators only; no runtime state. Placed after fsEntry so
|
|
// the FS rewriter runs first for `fs:/path` → `fs:<uuid>` before any
|
|
// downstream check that might chain app-root-dir → fs.
|
|
appPermission: AppPermissionService,
|
|
subdomainPermission: SubdomainPermissionService,
|
|
recommendedApps: RecommendedAppsService,
|
|
suggestedApps: SuggestedAppsService,
|
|
// Reaches `auth` and `permission` at call time only.
|
|
app: AppService,
|
|
socket: SocketService,
|
|
// Delivers through `socket` and resolves paths through `fs`, so it follows
|
|
// both; `fs` reaches back for dispatch at call time only.
|
|
events: EventsService,
|
|
notification: NotificationService,
|
|
// Declared after `auth` (origin → app uid resolution happens through
|
|
// AuthService.appUidFromOrigin).
|
|
appFeedback: AppFeedbackService,
|
|
broadcast: BroadcastService,
|
|
// Forwards through `broadcast` and puts deliveries down through `socket`,
|
|
// so it follows both; `events` reaches it at call time only.
|
|
eventForward: EventForwardService,
|
|
// Independent — only needs the event client and redis.
|
|
cacheReplication: CacheReplicationService,
|
|
oidc: OIDCService,
|
|
appIcon: AppIconService,
|
|
defaultUser: DefaultUserService,
|
|
homepage: PuterHomepageService,
|
|
// Health comes after socket so its default `socket-initialized`
|
|
// check can reference the peer.
|
|
health: ServerHealthService,
|
|
localworkerservice: LocalWorkerService,
|
|
} satisfies IPuterServiceRegistry;
|