From 86ad007e339772f63e1d3da43b33ba2e6a605e2b Mon Sep 17 00:00:00 2001 From: syui Date: Wed, 6 Nov 2024 08:11:30 +0900 Subject: [PATCH] fix --- repos_extra/.keep | 0 .../packages-rs/drainpipe/.env.local | 5 + .../frontpage/packages-rs/drainpipe/.keep | 0 .../packages-rs/drainpipe/Dockerfile | 20 ++ .../packages-rs/drainpipe/docker-compose.yml | 9 + .../packages/atproto-browser/Dockerfile | 10 + .../packages/atproto-browser/compose.yml | 8 + .../frontpage/packages/frontpage/.env.local | 14 ++ .../frontpage/packages/frontpage/Dockerfile | 17 ++ .../frontpage/packages/frontpage/compose.yml | 8 + .../frontpage/public/frontpage-logo.svg | 16 ++ repos_extra/frontpage/readme.md | 226 ++++++++++++++++++ repos_extra/pdsls/Dockerfile | 11 + repos_extra/pdsls/compose.yml | 6 + repos_extra/pdsls/vite.config.ts | 17 ++ repos_extra/python-oauth-web-app/Dockerfile | 7 + repos_extra/python-oauth-web-app/app_add.py | 3 + repos_extra/python-oauth-web-app/compose.yml | 10 + .../python-oauth-web-app/templates/about.html | 20 ++ .../python-oauth-web-app/templates/base.html | 114 +++++++++ .../templates/bsky_post.html | 8 + .../python-oauth-web-app/templates/error.html | 13 + .../templates/favicon.png | Bin 0 -> 14738 bytes .../templates/favicon.svg | 1 + .../python-oauth-web-app/templates/home.html | 29 +++ .../python-oauth-web-app/templates/login.html | 15 ++ repos_extra/readme.md | 5 + 27 files changed, 592 insertions(+) create mode 100644 repos_extra/.keep create mode 100644 repos_extra/frontpage/packages-rs/drainpipe/.env.local create mode 100644 repos_extra/frontpage/packages-rs/drainpipe/.keep create mode 100644 repos_extra/frontpage/packages-rs/drainpipe/Dockerfile create mode 100644 repos_extra/frontpage/packages-rs/drainpipe/docker-compose.yml create mode 100644 repos_extra/frontpage/packages/atproto-browser/Dockerfile create mode 100644 repos_extra/frontpage/packages/atproto-browser/compose.yml create mode 100644 repos_extra/frontpage/packages/frontpage/.env.local create mode 100644 repos_extra/frontpage/packages/frontpage/Dockerfile create mode 100644 repos_extra/frontpage/packages/frontpage/compose.yml create mode 100644 repos_extra/frontpage/packages/frontpage/public/frontpage-logo.svg create mode 100644 repos_extra/frontpage/readme.md create mode 100644 repos_extra/pdsls/Dockerfile create mode 100644 repos_extra/pdsls/compose.yml create mode 100644 repos_extra/pdsls/vite.config.ts create mode 100644 repos_extra/python-oauth-web-app/Dockerfile create mode 100644 repos_extra/python-oauth-web-app/app_add.py create mode 100644 repos_extra/python-oauth-web-app/compose.yml create mode 100644 repos_extra/python-oauth-web-app/templates/about.html create mode 100644 repos_extra/python-oauth-web-app/templates/base.html create mode 100644 repos_extra/python-oauth-web-app/templates/bsky_post.html create mode 100644 repos_extra/python-oauth-web-app/templates/error.html create mode 100644 repos_extra/python-oauth-web-app/templates/favicon.png create mode 100644 repos_extra/python-oauth-web-app/templates/favicon.svg create mode 100644 repos_extra/python-oauth-web-app/templates/home.html create mode 100644 repos_extra/python-oauth-web-app/templates/login.html create mode 100644 repos_extra/readme.md diff --git a/repos_extra/.keep b/repos_extra/.keep new file mode 100644 index 0000000..e69de29 diff --git a/repos_extra/frontpage/packages-rs/drainpipe/.env.local b/repos_extra/frontpage/packages-rs/drainpipe/.env.local new file mode 100644 index 0000000..efa053b --- /dev/null +++ b/repos_extra/frontpage/packages-rs/drainpipe/.env.local @@ -0,0 +1,5 @@ +DATABASE_URL="drainpipe.db" +FRONTPAGE_CONSUMER_URL="http://${cloudflared}/api/receive_hook" +FRONTPAGE_CONSUMER_SECRET=`openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32` + +#RELAY_URL=wss://syu.is diff --git a/repos_extra/frontpage/packages-rs/drainpipe/.keep b/repos_extra/frontpage/packages-rs/drainpipe/.keep new file mode 100644 index 0000000..e69de29 diff --git a/repos_extra/frontpage/packages-rs/drainpipe/Dockerfile b/repos_extra/frontpage/packages-rs/drainpipe/Dockerfile new file mode 100644 index 0000000..590140e --- /dev/null +++ b/repos_extra/frontpage/packages-rs/drainpipe/Dockerfile @@ -0,0 +1,20 @@ +FROM rust:1.78-alpine AS builder + +RUN apk add libressl-dev musl-dev sqlite-dev + +WORKDIR /usr/src/unravel +COPY . . +# TODO: Use cargo-chef to cache dependencies compilation independently of the binary +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/root/target \ + cargo build --release --package drainpipe && \ + # Move the release binary to a folder to be copied to the final image. It can't be copied directly from the target folder because it's in a cache mount + # See https://gist.github.com/noelbundick/6922d26667616e2ba5c3aff59f0824cd?permalink_comment_id=4379948#gistcomment-4379948 + mv ./target/release /root + +FROM alpine:3.14 +COPY --from=builder /root/release/drainpipe / + +ENV DATABASE_URL="/drainpipedata/drainpipe.db" + +ENTRYPOINT ["/drainpipe"] diff --git a/repos_extra/frontpage/packages-rs/drainpipe/docker-compose.yml b/repos_extra/frontpage/packages-rs/drainpipe/docker-compose.yml new file mode 100644 index 0000000..d5e214a --- /dev/null +++ b/repos_extra/frontpage/packages-rs/drainpipe/docker-compose.yml @@ -0,0 +1,9 @@ +services: + drainpipe: + build: + dockerfile: ./packages-rs/drainpipe/Dockerfile + context: ../../ + env_file: + - ./.env.local + volumes: + - ./drainpipedata:/drainpipedata diff --git a/repos_extra/frontpage/packages/atproto-browser/Dockerfile b/repos_extra/frontpage/packages/atproto-browser/Dockerfile new file mode 100644 index 0000000..e029727 --- /dev/null +++ b/repos_extra/frontpage/packages/atproto-browser/Dockerfile @@ -0,0 +1,10 @@ +FROM node:20 + +RUN npm install -g pnpm +WORKDIR /app +RUN git clone https://github.com/likeandscribe/frontpage +WORKDIR /app/frontpage/packages/atproto-browser +RUN pnpm i +RUN pnpm build + +CMD [ "pnpm", "start"] diff --git a/repos_extra/frontpage/packages/atproto-browser/compose.yml b/repos_extra/frontpage/packages/atproto-browser/compose.yml new file mode 100644 index 0000000..57a2d59 --- /dev/null +++ b/repos_extra/frontpage/packages/atproto-browser/compose.yml @@ -0,0 +1,8 @@ +services: + atbrowser: + build: + context: . + ports: + - "3000:3000" + environment: + - WATCHPACK_POLLING=true diff --git a/repos_extra/frontpage/packages/frontpage/.env.local b/repos_extra/frontpage/packages/frontpage/.env.local new file mode 100644 index 0000000..0b2e67f --- /dev/null +++ b/repos_extra/frontpage/packages/frontpage/.env.local @@ -0,0 +1,14 @@ +PRIVATE_JWK=`pnpm exec tsx ./scripts/generate-jwk.mts` +PUBLIC_JWK=`pnpm exec tsx ./scripts/generate-jwk.mts` + +TURSO_CONNECTION_URL=libsql://xxx.turso.io +#TURSO_CONNECTION_URL=`turso db shell xxx-xxx` +TURSO_AUTH_TOKEN=`turso db tokens create xxx-xxx` + +DRAINPIPE_CONSUMER_SECRET=`openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32` +VERCEL_PROJECT_PRODUCTION_URL=example.com +VERCEL_BRANCH_URL=example.com + +#DRAINPIPE_CONSUMER_SECRET=secret +#TURSO_CONNECTION_URL=libsql://turso.dev.unravel.fyi +#PLC_DIRECTORY_URL=https://plc.dev.unravel.fyi diff --git a/repos_extra/frontpage/packages/frontpage/Dockerfile b/repos_extra/frontpage/packages/frontpage/Dockerfile new file mode 100644 index 0000000..66ff8e4 --- /dev/null +++ b/repos_extra/frontpage/packages/frontpage/Dockerfile @@ -0,0 +1,17 @@ +FROM node:20 + +RUN npm install -g pnpm +WORKDIR /app +RUN git clone https://github.com/likeandscribe/frontpage +WORKDIR /app/frontpage +RUN pnpm i +RUN pnpm exec turbo run --affected type-check + +WORKDIR /app/frontpage/packages/frontpage +COPY ./.env.local ./.env.local +COPY ./app ./app +COPY ./lib ./lib +RUN pnpm run db:generate +RUN pnpm run db:migrate +RUN pnpm run build +CMD [ "pnpm", "run", "start"] diff --git a/repos_extra/frontpage/packages/frontpage/compose.yml b/repos_extra/frontpage/packages/frontpage/compose.yml new file mode 100644 index 0000000..e1d7db1 --- /dev/null +++ b/repos_extra/frontpage/packages/frontpage/compose.yml @@ -0,0 +1,8 @@ +services: + frontpage: + build: + context: . + ports: + - "3000:3000" + environment: + - WATCHPACK_POLLING=true diff --git a/repos_extra/frontpage/packages/frontpage/public/frontpage-logo.svg b/repos_extra/frontpage/packages/frontpage/public/frontpage-logo.svg new file mode 100644 index 0000000..bd39143 --- /dev/null +++ b/repos_extra/frontpage/packages/frontpage/public/frontpage-logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/repos_extra/frontpage/readme.md b/repos_extra/frontpage/readme.md new file mode 100644 index 0000000..e59d560 --- /dev/null +++ b/repos_extra/frontpage/readme.md @@ -0,0 +1,226 @@ +# frontpage + +- https://frontpage.fyi +- https://bsky.app/profile/frontpage.fyi +- https://github.com/likeandscribe/frontpage + +```sh +$ git clone https://github.com/likeandscribe/frontpage +$ dir=${0:a:h}/frontpage +$ cd $dir +``` + +## first setting + +```sh +$ cd $dir +$ nvm use 20 +$ pnpm i +$ cat turbo.json +$ pnpm exec turbo run --affected type-check +``` + +```sh +$ cd $dir/packages/frontpage +$ pnpm exec tsx ./scripts/generate-jwk.mts +# pnpm run db:generate +# pnpm run db:migrate +$ cat .env.local +``` + +```sh +# frontpage/.env.local +PRIVATE_JWK=`pnpm exec tsx ./scripts/generate-jwk.mts` +PUBLIC_JWK=`pnpm exec tsx ./scripts/generate-jwk.mts` + +TURSO_CONNECTION_URL=libsql://xxx.turso.io +#TURSO_CONNECTION_URL=`turso db shell xxx-xxx` +TURSO_AUTH_TOKEN=`turso db tokens create xxx-xxx` + +DRAINPIPE_CONSUMER_SECRET=`openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32` +VERCEL_PROJECT_PRODUCTION_URL=example.com +VERCEL_BRANCH_URL=example.com + +#DRAINPIPE_CONSUMER_SECRET=secret +#TURSO_CONNECTION_URL=libsql://turso.dev.unravel.fyi +#PLC_DIRECTORY_URL=https://plc.dev.unravel.fyi +``` + +```sh +$ cd $dir/packages-rs/drainpipe +$ cargo install diesel_cli --no-default-features --features sqlite +$ diesel setup +$ diesel migration run +$ ls drainpipe.db +$ cat .env.local +``` + +```sh +# drainpipe/.env.local +DATABASE_URL="drainpipe.db" +FRONTPAGE_CONSUMER_URL="http://${cloudflared}/api/receive_hook" +FRONTPAGE_CONSUMER_SECRET=`openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32` +#RELAY_URL=wss://bsky.network +#FRONTPAGE_CONSUMER_SECRET=secret +``` + +## rewrite + +```sh +$ cd $dir/packages/frontpage +$ PUBLIC_URL=example.com +$ grep -R frontpage.fyi ./app ./lib |cut -d : -f 1|sed -i "s/frontpage.fyi/$PUBLIC_URL/g" + +$ HOST_REVERT=com.unravel.example +$ grep -R unravel.frontpage ./app ./lib |cut -d : -f 1|xargs sed -i "s/fyi.unravel.frontpage/${HOST_REVERT}/g" +``` + +```sh +$ cd $dir/packages-rs/drainpipe +$ HOST_REVERT=com.unravel.example +$ grep -R fyi.unravel.frontpage ./src |cut -d : -f 1|xargs sed -i "s/fyi.unravel.frontpage/${HOST_REVERT}/g" +``` + +## deploy + +```sh +$ cd $dir/packages-rs/drainpipe +$ docker compose up +--- +$ cd $dir/packages/frontpage +$ docker compose up +``` + +## explanation + +### client-metadata.json + +the `client_id` is different between `pnpm run start` and `pnpm run dev`. see `https://localhost:3000/oauth/client-metadata.json` for this. + +### local-infra + +i think this is the server configuration required for self-hosting. + +https://github.com/likeandscribe/frontpage/tree/main/packages/frontpage/local-infra + +since plc gives an error, do the following. probably a postgres database is required. there is no need to open ports. + +```yml + plc: + image: ghcr.io/bluesky-social/did-method-plc:plc-f2ab7516bac5bc0f3f86842fa94e996bd1b3815b + container_name: plc + restart: unless-stopped + ports: + - '4000:8080' + depends_on: + - plc_db + env_file: + - ./plc.env + + plc_db: + image: postgres:16-alpine + restart: always + env_file: + - ./postgres.env + volumes: + - ./configs/postgres/init/:/docker-entrypoint-initdb.d/ + - ./data/postgres/:/var/lib/postgresql/data/ + healthcheck: + test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB" + interval: 5s + retries: 20 +``` + +```sh +# plc.env +DEBUG_MODE=1 +LOG_ENABLED=true +LOG_LEVEL=debug +LOG_DESTINATION=1 +PORT=8080 +DATABASE_URL=postgres://postgres:postgres@plc_db/plc +DB_CREDS_JSON='{"username":"postgres","password":"postgres","host":"plc_db","port":"5432","database":"plc"}' +ENABLE_MIGRATIONS=true +DB_MIGRATE_CREDS_JSON='{"username":"postgres","password":"postgres","host":"plc_db","port":"5432","database":"plc"}' +``` + +```sh +# configs/postgres/init/init.sql +-- PLC +CREATE DATABASE plc; +GRANT ALL PRIVILEGES ON DATABASE plc TO postgres; +``` + +### pds + +first, i think you need to get the pdsurl with oauth(session). if you have a session, you can perform operations such as posting. + +```sh +$ cd $dir/packages/frontpage +./lib/data/user.ts: const pdsUrl = await getPdsUrl(session.user.did); +``` + +it seems that drainpipe searches for `fyi.unravel.frontpage(collection)` in pds and commits it to firehose subscriberepos. if you change these two parts, it will not work with `frontpage.fyi`. + +```rust +// https://github.com/likeandscribe/frontpage/blob/e7444ec6c19f0ccef3776f04702c3bb033ed3bfc/packages-rs/drainpipe/src/main.rs#L66-L97 +// RELAY_URL=wss://bsky.network +let mut ws_request = format!( + "{}/xrpc/com.atproto.sync.subscribeRepos{}", + relay_url, query_string +) + +/// Process a message from the firehose. Returns the sequence number of the message or an error. +async fn process(message: Vec, ctx: &mut Context) -> Result { + let (_header, data) = firehose::read(&message).map_err(|e| ProcessError { + inner: e.into(), + seq: -1, + source: message.clone().into(), + kind: ProcessErrorKind::DecodeError, + })?; + let sequence = match data { + firehose::SubscribeRepos::Commit(commit) => { + let frontpage_ops = commit + .operations + .iter() + .filter(|op| op.path.starts_with("com.unravel.example.")) + //.filter(|op| op.path.starts_with("fyi.unravel.frontpage.")) + .collect::>(); + if !frontpage_ops.is_empty() { + process_frontpage_ops(&frontpage_ops, &commit, &ctx) + .map_err(|e| ProcessError { + seq: commit.sequence, + inner: e, + source: message.clone().into(), + kind: ProcessErrorKind::ProcessError, + }) + .await?; + } + commit.sequence + } + msg => msg.sequence(), + }; + + Ok(sequence) +} +``` + +## other + +### license view + +```html +// https://github.com/likeandscribe/frontpage/blob/de31aedf73c4e80e7376cf73c7c054437563f2ab/packages/frontpage/app/layout.tsx#L28-L52 ++
'}} /> +``` + +### admin view + +```sh +# https://github.com/likeandscribe/frontpage/blob/7fccf20fa800ba25fd57db279033ddf2cc92e9ce/packages/frontpage/lib/constants.ts +./lib/constants.ts:export const FRONTPAGE_ATPROTO_HANDLE = "admin.example.com"; + +# https://github.com/likeandscribe/frontpage/blob/cf8a4cb8bc7bab54407972964f8d39bf5e7c9182/packages/frontpage/app/(app)/layout.tsx#L55-L66 +./app/\(app\)/layout.tsx:@admin.example.com +``` + diff --git a/repos_extra/pdsls/Dockerfile b/repos_extra/pdsls/Dockerfile new file mode 100644 index 0000000..1dc6678 --- /dev/null +++ b/repos_extra/pdsls/Dockerfile @@ -0,0 +1,11 @@ +FROM node:20 + +RUN npm install -g pnpm +WORKDIR /app +RUN git clone https://github.com/notjuliet/pdsls +WORKDIR /app/pdsls +COPY ./vite.config.ts ./vite.config.ts +RUN pnpm i +RUN pnpm build + +CMD [ "pnpm", "start"] diff --git a/repos_extra/pdsls/compose.yml b/repos_extra/pdsls/compose.yml new file mode 100644 index 0000000..06a46f0 --- /dev/null +++ b/repos_extra/pdsls/compose.yml @@ -0,0 +1,6 @@ +services: + atbrowser: + build: + context: . + ports: + - 3000:13213 diff --git a/repos_extra/pdsls/vite.config.ts b/repos_extra/pdsls/vite.config.ts new file mode 100644 index 0000000..97b0ef6 --- /dev/null +++ b/repos_extra/pdsls/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from "vite"; +import solidPlugin from "vite-plugin-solid"; +import UnoCSS from "unocss/vite"; + +const SERVER_HOST = "0.0.0.0"; +const SERVER_PORT = 13213; + +export default defineConfig({ + plugins: [UnoCSS(), solidPlugin()], + server: { + host: SERVER_HOST, + port: SERVER_PORT, + }, + build: { + target: "esnext", + }, +}); diff --git a/repos_extra/python-oauth-web-app/Dockerfile b/repos_extra/python-oauth-web-app/Dockerfile new file mode 100644 index 0000000..42dcf64 --- /dev/null +++ b/repos_extra/python-oauth-web-app/Dockerfile @@ -0,0 +1,7 @@ +FROM syui/aios + +WORKDIR /app +COPY . . +RUN pacman -Syu rye --noconfirm +RUN rye sync + diff --git a/repos_extra/python-oauth-web-app/app_add.py b/repos_extra/python-oauth-web-app/app_add.py new file mode 100644 index 0000000..f8e0492 --- /dev/null +++ b/repos_extra/python-oauth-web-app/app_add.py @@ -0,0 +1,3 @@ +@app.route("/about") +def aboutpage(): + return render_template("about.html") diff --git a/repos_extra/python-oauth-web-app/compose.yml b/repos_extra/python-oauth-web-app/compose.yml new file mode 100644 index 0000000..f915dd5 --- /dev/null +++ b/repos_extra/python-oauth-web-app/compose.yml @@ -0,0 +1,10 @@ +services: + web: + build: . + env_file: + - ./.env + ports: + - "5000:5000" + volumes: + - ./demo.sqlite:/app/demo.sqlite + command: rye run flask run --host=0.0.0.0 diff --git a/repos_extra/python-oauth-web-app/templates/about.html b/repos_extra/python-oauth-web-app/templates/about.html new file mode 100644 index 0000000..f9a1a42 --- /dev/null +++ b/repos_extra/python-oauth-web-app/templates/about.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} +{% block content %} + +

service

+

This service allows you to comment on live broadcasts using your atproto account.

+ +

system

+

Display posts to BBS using atproto oauth.

+

This service is generated using bluesky/cookbook.

+

Authentication information will be deleted periodically.

+
+ +

サービス

+

このサービスはlive配信にatprotoアカウントを使ってコメントができます。

+

システム

+

atproto oauthを使用してbbsへの書き込みを許可します。

+

このサービスはbluesky/cookbookを使用して生成されています。

+

認証情報は定期的に削除されます。

+ +{% endblock %} diff --git a/repos_extra/python-oauth-web-app/templates/base.html b/repos_extra/python-oauth-web-app/templates/base.html new file mode 100644 index 0000000..710fd4d --- /dev/null +++ b/repos_extra/python-oauth-web-app/templates/base.html @@ -0,0 +1,114 @@ + + + + + + + + + + o.syui.ai + + +
+
+ {% if g.user %} + {% endif %} +
+ +
+ +
+
+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + {% block content %}{% endblock %} +
+
+ + +
©syui
+ + diff --git a/repos_extra/python-oauth-web-app/templates/bsky_post.html b/repos_extra/python-oauth-web-app/templates/bsky_post.html new file mode 100644 index 0000000..acec60e --- /dev/null +++ b/repos_extra/python-oauth-web-app/templates/bsky_post.html @@ -0,0 +1,8 @@ +{% extends 'base.html' %} + +{% block content %} +
+ + +
+{% endblock %} diff --git a/repos_extra/python-oauth-web-app/templates/error.html b/repos_extra/python-oauth-web-app/templates/error.html new file mode 100644 index 0000000..76fc860 --- /dev/null +++ b/repos_extra/python-oauth-web-app/templates/error.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} + +{% block title %}Error{% endblock %} + +{% block content %} +

⚠️ Error {{ status_code }} ⚠️

+{% if err.description %} +

{{ err.description }}

+{% else %} +

Something went wrong!

+{% endif %} +

Start Over

+{% endblock %} diff --git a/repos_extra/python-oauth-web-app/templates/favicon.png b/repos_extra/python-oauth-web-app/templates/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..6aeb218a19834f5401e59a50834cfd11f2834544 GIT binary patch literal 14738 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 L+^M \ No newline at end of file diff --git a/repos_extra/python-oauth-web-app/templates/home.html b/repos_extra/python-oauth-web-app/templates/home.html new file mode 100644 index 0000000..1d56aaf --- /dev/null +++ b/repos_extra/python-oauth-web-app/templates/home.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} +{% block content %} + +
+
+
+ +
+
+
+ {% if g.user %} + @{{ g.user['handle'] }} +
+ +
+ {% else %} +
+ + +
+
+

write comment using oauth atproto.

+
+ {% endif %} +
+ +
+ +{% endblock %} diff --git a/repos_extra/python-oauth-web-app/templates/login.html b/repos_extra/python-oauth-web-app/templates/login.html new file mode 100644 index 0000000..a000403 --- /dev/null +++ b/repos_extra/python-oauth-web-app/templates/login.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Login with atproto

+
+

Provide your handle or DID to authorize an existing account with PDS. +
You can also supply a PDS/entryway URL (eg, https://pds.example.com).

+
+ + +
+
+
+{% endblock %} diff --git a/repos_extra/readme.md b/repos_extra/readme.md new file mode 100644 index 0000000..110b8e6 --- /dev/null +++ b/repos_extra/readme.md @@ -0,0 +1,5 @@ +https://github.com/notjuliet/pdsls + +https://github.com/likeandscribe/frontpage + +https://github.com/bluesky-social/cookbook/tree/main/python-oauth-web-app