appview/pages: terms & privacy pages #409

merged
opened by anirudh.fi targeting master from push-qlzpkvltqlzm
Changed files
+240 -1
appview
pages
templates
state
+16
appview/pages/pages.go
···
return p.executePlain("user/completeSignup", w, params)
}
type TimelineParams struct {
LoggedInUser *oauth.User
Timeline []db.TimelineEvent
···
return p.executePlain("user/completeSignup", w, params)
}
+
type TermsOfServiceParams struct {
+
LoggedInUser *oauth.User
+
}
+
+
func (p *Pages) TermsOfService(w io.Writer, params TermsOfServiceParams) error {
+
return p.execute("legal/terms", w, params)
+
}
+
+
type PrivacyPolicyParams struct {
+
LoggedInUser *oauth.User
+
}
+
+
func (p *Pages) PrivacyPolicy(w io.Writer, params PrivacyPolicyParams) error {
+
return p.execute("legal/privacy", w, params)
+
}
+
type TimelineParams struct {
LoggedInUser *oauth.User
Timeline []db.TimelineEvent
+8 -1
appview/pages/templates/layouts/footer.html
···
{{ define "layouts/footer" }}
<div class="w-full p-4 bg-white dark:bg-gray-800 rounded-t drop-shadow-sm">
<div class="container mx-auto text-center text-gray-600 dark:text-gray-400 text-sm">
-
<span class="font-semibold italic">tangled</span> &mdash; made by <a href="/@oppi.li">@oppi.li</a> and <a href="/@icyphox.sh">@icyphox.sh</a>
</div>
</div>
{{ end }}
···
{{ define "layouts/footer" }}
<div class="w-full p-4 bg-white dark:bg-gray-800 rounded-t drop-shadow-sm">
<div class="container mx-auto text-center text-gray-600 dark:text-gray-400 text-sm">
+
<div class="mb-2">
+
<a href="/terms" class="hover:text-gray-900 dark:hover:text-gray-200 underline">Terms of Service</a>
+
&nbsp;•&nbsp;
+
<a href="/privacy" class="hover:text-gray-900 dark:hover:text-gray-200 underline">Privacy Policy</a>
+
</div>
+
<div>
+
<span class="font-semibold italic">tangled</span> &mdash; made by <a href="/@oppi.li">@oppi.li</a> and <a href="/@icyphox.sh">@icyphox.sh</a>
+
</div>
</div>
</div>
{{ end }}
+131
appview/pages/templates/legal/privacy.html
···
···
+
{{ define "title" }} privacy policy {{ end }}
+
{{ define "content" }}
+
<div class="max-w-4xl mx-auto px-4 py-8">
+
<div class="prose prose-gray dark:prose-invert max-w-none">
+
<h1>Privacy Policy</h1>
+
+
<p><strong>Last updated:</strong> {{ now.Format "January 2, 2006" }}</p>
+
+
<p>This Privacy Policy describes how Tangled ("we," "us," or "our") collects, uses, and shares your personal information when you use our platform and services (the "Service").</p>
+
+
<h2>1. Information We Collect</h2>
+
+
<h3>Account Information</h3>
+
<p>When you create an account, we collect:</p>
+
<ul>
+
<li>Your chosen username</li>
+
<li>Email address</li>
+
<li>Profile information you choose to provide</li>
+
<li>Authentication data</li>
+
</ul>
+
+
<h3>Content and Activity</h3>
+
<p>We store:</p>
+
<ul>
+
<li>Code repositories and associated metadata</li>
+
<li>Issues, pull requests, and comments</li>
+
<li>Activity logs and usage patterns</li>
+
<li>Public keys for authentication</li>
+
</ul>
+
+
<h2>2. Data Location and Hosting</h2>
+
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4 my-6">
+
<h3 class="text-blue-800 dark:text-blue-200 font-semibold mb-2">EU Data Hosting</h3>
+
<p class="text-blue-700 dark:text-blue-300">
+
<strong>All Tangled service data is hosted within the European Union.</strong> Specifically:
+
</p>
+
<ul class="text-blue-700 dark:text-blue-300 mt-2">
+
<li><strong>Personal Data Servers (PDS):</strong> Accounts hosted on Tangled PDS (*.tngl.sh) are located in Finland</li>
+
<li><strong>Application Data:</strong> All other service data is stored on EU-based servers</li>
+
<li><strong>Data Processing:</strong> All data processing occurs within EU jurisdiction</li>
+
</ul>
+
</div>
+
+
<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4 my-6">
+
<h3 class="text-yellow-800 dark:text-yellow-200 font-semibold mb-2">External PDS Notice</h3>
+
<p class="text-yellow-700 dark:text-yellow-300">
+
<strong>Important:</strong> If your account is hosted on Bluesky's PDS or other self-hosted Personal Data Servers (not *.tngl.sh), we do not control that data. The data protection, storage location, and privacy practices for such accounts are governed by the respective PDS provider's policies, not this Privacy Policy. We only control data processing within our own services and infrastructure.
+
</p>
+
</div>
+
+
<h2>3. Third-Party Data Processors</h2>
+
<p>We only share your data with the following third-party processors:</p>
+
+
<h3>Resend (Email Services)</h3>
+
<ul>
+
<li><strong>Purpose:</strong> Sending transactional emails (account verification, notifications)</li>
+
<li><strong>Data Shared:</strong> Email address and necessary message content</li>
+
<li><strong>Location:</strong> EU-compliant email delivery service</li>
+
</ul>
+
+
<h3>Cloudflare (Image Caching)</h3>
+
<ul>
+
<li><strong>Purpose:</strong> Caching and optimizing image delivery</li>
+
<li><strong>Data Shared:</strong> Public images and associated metadata for caching purposes</li>
+
<li><strong>Location:</strong> Global CDN with EU data protection compliance</li>
+
</ul>
+
+
<h2>4. How We Use Your Information</h2>
+
<p>We use your information to:</p>
+
<ul>
+
<li>Provide and maintain the Service</li>
+
<li>Process your transactions and requests</li>
+
<li>Send you technical notices and support messages</li>
+
<li>Improve and develop new features</li>
+
<li>Ensure security and prevent fraud</li>
+
<li>Comply with legal obligations</li>
+
</ul>
+
+
<h2>5. Data Sharing and Disclosure</h2>
+
<p>We do not sell, trade, or rent your personal information. We may share your information only in the following circumstances:</p>
+
<ul>
+
<li>With the third-party processors listed above</li>
+
<li>When required by law or legal process</li>
+
<li>To protect our rights, property, or safety, or that of our users</li>
+
<li>In connection with a merger, acquisition, or sale of assets (with appropriate protections)</li>
+
</ul>
+
+
<h2>6. Data Security</h2>
+
<p>We implement appropriate technical and organizational measures to protect your personal information against unauthorized access, alteration, disclosure, or destruction. However, no method of transmission over the Internet is 100% secure.</p>
+
+
<h2>7. Data Retention</h2>
+
<p>We retain your personal information for as long as necessary to provide the Service and fulfill the purposes outlined in this Privacy Policy, unless a longer retention period is required by law.</p>
+
+
<h2>8. Your Rights</h2>
+
<p>Under applicable data protection laws, you have the right to:</p>
+
<ul>
+
<li>Access your personal information</li>
+
<li>Correct inaccurate information</li>
+
<li>Request deletion of your information</li>
+
<li>Object to processing of your information</li>
+
<li>Data portability</li>
+
<li>Withdraw consent (where applicable)</li>
+
</ul>
+
+
<h2>9. Cookies and Tracking</h2>
+
<p>We use cookies and similar technologies to:</p>
+
<ul>
+
<li>Maintain your login session</li>
+
<li>Remember your preferences</li>
+
<li>Analyze usage patterns to improve the Service</li>
+
</ul>
+
<p>You can control cookie settings through your browser preferences.</p>
+
+
<h2>10. Children's Privacy</h2>
+
<p>The Service is not intended for children under 16 years of age. We do not knowingly collect personal information from children under 16. If we become aware that we have collected such information, we will take steps to delete it.</p>
+
+
<h2>11. International Data Transfers</h2>
+
<p>While all our primary data processing occurs within the EU, some of our third-party processors may process data outside the EU. When this occurs, we ensure appropriate safeguards are in place, such as Standard Contractual Clauses or adequacy decisions.</p>
+
+
<h2>12. Changes to This Privacy Policy</h2>
+
<p>We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the "Last updated" date.</p>
+
+
<h2>13. Contact Information</h2>
+
<p>If you have any questions about this Privacy Policy or wish to exercise your rights, please contact us through our platform or via email.</p>
+
+
<div class="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700 text-sm text-gray-600 dark:text-gray-400">
+
<p>This Privacy Policy complies with the EU General Data Protection Regulation (GDPR) and other applicable data protection laws.</p>
+
</div>
+
</div>
+
</div>
+
{{ end }}
+69
appview/pages/templates/legal/terms.html
···
···
+
{{ define "title" }}terms of service{{ end }}
+
+
{{ define "content" }}
+
<div class="max-w-4xl mx-auto px-4 py-8">
+
<div class="prose prose-gray dark:prose-invert max-w-none">
+
<h1>Terms of Service</h1>
+
+
<p><strong>Last updated:</strong> {{ now.Format "January 2, 2006" }}</p>
+
+
<p>Welcome to Tangled. These Terms of Service ("Terms") govern your access to and use of the Tangled platform and services (the "Service") operated by us ("Tangled," "we," "us," or "our").</p>
+
+
<h2>1. Acceptance of Terms</h2>
+
<p>By accessing or using our Service, you agree to be bound by these Terms. If you disagree with any part of these terms, then you may not access the Service.</p>
+
+
<h2>2. Account Registration</h2>
+
<p>To use certain features of the Service, you must register for an account. You agree to provide accurate, current, and complete information during the registration process and to update such information to keep it accurate, current, and complete.</p>
+
+
<h2>3. Account Termination</h2>
+
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 my-6">
+
<h3 class="text-red-800 dark:text-red-200 font-semibold mb-2">Important Notice</h3>
+
<p class="text-red-700 dark:text-red-300">
+
<strong>We reserve the right to terminate, suspend, or restrict access to your account at any time, for any reason, or for no reason at all, at our sole discretion.</strong> This includes, but is not limited to, termination for violation of these Terms, inappropriate conduct, spam, abuse, or any other behavior we deem harmful to the Service or other users.
+
</p>
+
<p class="text-red-700 dark:text-red-300 mt-2">
+
Account termination may result in the loss of access to your repositories, data, and other content associated with your account. We are not obligated to provide advance notice of termination, though we may do so in our discretion.
+
</p>
+
</div>
+
+
<h2>4. Acceptable Use</h2>
+
<p>You agree not to use the Service to:</p>
+
<ul>
+
<li>Violate any applicable laws or regulations</li>
+
<li>Infringe upon the rights of others</li>
+
<li>Upload, store, or share content that is illegal, harmful, threatening, abusive, harassing, defamatory, vulgar, obscene, or otherwise objectionable</li>
+
<li>Engage in spam, phishing, or other deceptive practices</li>
+
<li>Attempt to gain unauthorized access to the Service or other users' accounts</li>
+
<li>Interfere with or disrupt the Service or servers connected to the Service</li>
+
</ul>
+
+
<h2>5. Content and Intellectual Property</h2>
+
<p>You retain ownership of the content you upload to the Service. By uploading content, you grant us a non-exclusive, worldwide, royalty-free license to use, reproduce, modify, and distribute your content as necessary to provide the Service.</p>
+
+
<h2>6. Privacy</h2>
+
<p>Your privacy is important to us. Please review our <a href="/privacy" class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300">Privacy Policy</a>, which also governs your use of the Service.</p>
+
+
<h2>7. Disclaimers</h2>
+
<p>The Service is provided on an "AS IS" and "AS AVAILABLE" basis. We make no warranties, expressed or implied, and hereby disclaim and negate all other warranties including without limitation, implied warranties or conditions of merchantability, fitness for a particular purpose, or non-infringement of intellectual property or other violation of rights.</p>
+
+
<h2>8. Limitation of Liability</h2>
+
<p>In no event shall Tangled, nor its directors, employees, partners, agents, suppliers, or affiliates, be liable for any indirect, incidental, special, consequential, or punitive damages, including without limitation, loss of profits, data, use, goodwill, or other intangible losses, resulting from your use of the Service.</p>
+
+
<h2>9. Indemnification</h2>
+
<p>You agree to defend, indemnify, and hold harmless Tangled and its affiliates, officers, directors, employees, and agents from and against any and all claims, damages, obligations, losses, liabilities, costs, or debt, and expenses (including attorney's fees).</p>
+
+
<h2>10. Governing Law</h2>
+
<p>These Terms shall be interpreted and governed by the laws of Finland, without regard to its conflict of law provisions.</p>
+
+
<h2>11. Changes to Terms</h2>
+
<p>We reserve the right to modify or replace these Terms at any time. If a revision is material, we will try to provide at least 30 days notice prior to any new terms taking effect.</p>
+
+
<h2>12. Contact Information</h2>
+
<p>If you have any questions about these Terms of Service, please contact us through our platform or via email.</p>
+
+
<div class="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700 text-sm text-gray-600 dark:text-gray-400">
+
<p>These terms are effective as of the last updated date shown above and will remain in effect except with respect to any changes in their provisions in the future, which will be in effect immediately after being posted on this page.</p>
+
</div>
+
</div>
+
</div>
+
{{ end }}
+2
appview/state/router.go
···
r.Mount("/", s.OAuthRouter())
r.Get("/keys/{user}", s.Keys)
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
s.pages.Error404(w)
···
r.Mount("/", s.OAuthRouter())
r.Get("/keys/{user}", s.Keys)
+
r.Get("/terms", s.TermsOfService)
+
r.Get("/privacy", s.PrivacyPolicy)
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
s.pages.Error404(w)
+14
appview/state/state.go
···
return state, nil
}
func (s *State) Timeline(w http.ResponseWriter, r *http.Request) {
user := s.oauth.GetUser(r)
···
return state, nil
}
+
func (s *State) TermsOfService(w http.ResponseWriter, r *http.Request) {
+
user := s.oauth.GetUser(r)
+
s.pages.TermsOfService(w, pages.TermsOfServiceParams{
+
LoggedInUser: user,
+
})
+
}
+
+
func (s *State) PrivacyPolicy(w http.ResponseWriter, r *http.Request) {
+
user := s.oauth.GetUser(r)
+
s.pages.PrivacyPolicy(w, pages.PrivacyPolicyParams{
+
LoggedInUser: user,
+
})
+
}
+
func (s *State) Timeline(w http.ResponseWriter, r *http.Request) {
user := s.oauth.GetUser(r)