lingo-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev 8af89d2cc9f8
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 7efc97d0dde0b2a3e729ccf9
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
policy.json
{
  "auth": {
    "anonymous": true
  },
  "learners/$userId": {
    "description": "A learner's settings, streak, daily XP and total XP, as a JSON string. Only the learner can read or write it. The device keeps the same data, so progress works offline.",
    "fields": {
      "data": "String",
      "at": "UInt"
    },
    "rules": {
      "read": "@user.id != null && @user.id == $userId",
      "create": "@user.id != null && @user.id == $userId && @StringUtils.length(@newData.data) <= 100000",
      "update": "@user.id != null && @user.id == $userId && @StringUtils.length(@newData.data) <= 100000",
      "delete": "false"
    }
  },
  "learners/$userId/courses/$lang": {
    "description": "A learner's progress in one course (lessons done, review schedule per word and sentence, recent mistakes), as a JSON string. Only the learner can read or write it.",
    "fields": {
      "data": "String",
      "at": "UInt"
    },
    "rules": {
      "read": "@user.id != null && @user.id == $userId",
      "create": "@user.id != null && @user.id == $userId && @StringUtils.length($lang) == 2 && @StringUtils.length(@newData.data) <= 300000",
      "update": "@user.id != null && @user.id == $userId && @StringUtils.length(@newData.data) <= 300000",
      "delete": "false"
    }
  },
  "learners/$userId/xp/$logId": {
    "description": "Append-only log of the XP a learner adds to the weekly league board. Every XP gain on the board appends one entry in the same write, and xp_per_hour caps each learner at 600 XP in any hour, so nobody can flood the board.",
    "tier": "durable",
    "fields": {
      "amount": "UInt!",
      "week": "String!"
    },
    "rules": {
      "read": "@user.id != null && @user.id == $userId",
      "create": "@user.id != null && @user.id == $userId && @newData.amount >= 1",
      "update": "false",
      "delete": "false"
    },
    "invariants": [
      {
        "type": "rollingSum",
        "name": "xp_per_hour",
        "field": "amount",
        "windowSeconds": 3600,
        "limit": 600,
        "scopeVariable": "$userId"
      }
    ]
  },
  "weeks/$week/learners/$userId": {
    "description": "A learner's row on the weekly league board, public. $week is the week's start (Monday 00:00 UTC) in unix seconds. xp only grows together with a new entry in the learner's XP log (last), in the same write and for the current week.",
    "fields": {
      "name": "String",
      "lang": "String",
      "xp": "UInt",
      "last": "String",
      "hidden": "Bool"
    },
    "rules": {
      "read": "true",
      "create": "@user.id != null && @user.id == $userId && @StringUtils.length(@newData.name) >= 2 && @StringUtils.length(@newData.name) <= 24 && @StringUtils.length(@newData.lang) == 2 && (($week * 1 - 345600) // 604800) * 604800 + 345600 == $week * 1 && $week * 1 <= @time.now + 600 && @time.now < $week * 1 + 605400 && get(/learners/$userId/xp/@newData.last) == null && getAfter(/learners/$userId/xp/@newData.last).week == $week && @newData.xp == getAfter(/learners/$userId/xp/@newData.last).amount",
      "update": "@user.id != null && @user.id == $userId && @StringUtils.length(@newData.name) >= 2 && @StringUtils.length(@newData.name) <= 24 && @StringUtils.length(@newData.lang) == 2 && (@newData.xp == @data.xp || ($week * 1 <= @time.now + 600 && @time.now < $week * 1 + 605400 && get(/learners/$userId/xp/@newData.last) == null && getAfter(/learners/$userId/xp/@newData.last).week == $week && @newData.xp == @data.xp + getAfter(/learners/$userId/xp/@newData.last).amount))",
      "delete": "false"
    }
  },
  "functions": {
    "api": {
      "auth": "true",
      "entry": "functions/api.ts",
      "public": true,
      "methods": [
        "GET"
      ],
      "cors": "passthrough"
    }
  },
  "constants": {
    "AGENT_ADDRESS": "Cq2qrAwhb35z5FxjG1sKo1Gy8weUdXJFgZJwbjSbhkgB",
    "AGENT_ADDRESS_BOUND": true
  }
}

Change history

Point OpenApps links to openapps.free
8af89d2cc9 · 17 hours ago
Bind agent address in policy
0018350275 · 19 hours ago
Use OpenApps service URLs and add contribution strip
805365daf4 · 19 hours ago
Bounded source sync
3f2fb3b265 · 2 days ago
Bounded source sync
7dcb580324 · 2 days ago
Bounded source sync
dcd05c0944 · 2 days ago