pull/52/head
sunface 4 years ago
parent 3e19434047
commit a5b8b36fa9

@ -35,4 +35,9 @@ posts:
title_max_len: 128
brief_max_len: 128
# whether allow writing posts
writing_enabled: true
writing_enabled: true
#################################### SMTP ##############################
smtp:
from_address: "hello@im.dev"
from_name: "Im'dev"

@ -11,6 +11,7 @@ require (
github.com/gosimple/slug v1.9.0
github.com/grafana/grafana v5.4.5+incompatible
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
github.com/lithammer/shortuuid/v3 v3.0.5
github.com/mattn/go-sqlite3 v1.14.6
github.com/spf13/cobra v1.1.1

@ -141,6 +141,8 @@ github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac/go.mod h1:cO
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=

@ -17,10 +17,11 @@ import {
ModalBody,
useDisclosure,
Input,
useToast
useToast,
Heading
} from "@chakra-ui/react"
import Logo from "components/logo"
import { FaEnvelope, FaGithub } from "react-icons/fa"
import { FaEnvelope, FaGithub, FaPlane, FaRegPaperPlane } from "react-icons/fa"
import { requestApi } from "utils/axios/request"
import { saveToken } from "utils/axios/getToken"
import storage from "utils/localStorage"
@ -32,9 +33,10 @@ const LoginPage = () => {
const { isOpen, onOpen, onClose } = useDisclosure()
const toast = useToast()
const router = useRouter()
const [email,setEmail] = useState('')
const login = async (email:string) => {
const res = await requestApi.post("/user/login",{email: email})
const [email, setEmail] = useState('')
const [emailLogined, setEmailLogined] = useState(false)
const login = async (email: string) => {
const res = await requestApi.post("/user/login", { email: email })
saveToken(res.data.token)
storage.set('session', res.data)
const oldPage = storage.get('current-page')
@ -47,7 +49,7 @@ const LoginPage = () => {
}
const onEmailLogin = async () => {
const err = await validateEmail(email,false)
const err = await validateEmail(email, false)
if (err) {
toast({
description: err,
@ -55,10 +57,13 @@ const LoginPage = () => {
duration: 2000,
isClosable: true,
})
return
return
}
login(email)
await requestApi.post("/user/login/email", { email: email })
onClose()
setEmailLogined(true)
// login(email)
}
return (
@ -67,32 +72,40 @@ const LoginPage = () => {
<Box textAlign="center" display="flex" alignItems="center" flexDirection="column">
<Logo width="12rem" />
<Text mt="8" fontSize="1.1rem" fontWeight="500">im.dev</Text>
<VStack mt="2" p="5" align="left" spacing="2" fontSize="15px">
<Box display="flex" flexDirection="row" alignItems="center">
<svg width="48px" height="48px" fill={useColorModeValue("teal", "white")} version="1.1" viewBox="0 0 24 24"><path d="M7.036 14.836a1.003 1.003 0 01-1.418 0l-.709-.709a6.518 6.518 0 119.218-9.218l.71.71a1.003 1.003 0 010 1.417l-.71.71a1.003 1.003 0 01-1.418 0L12 7.035A3.51 3.51 0 007.036 12l.71.71a1.003 1.003 0 010 1.417l-.71.71zm2.128 3.546a1.003 1.003 0 010-1.418l.709-.71a1.003 1.003 0 011.418 0l.709.71A3.51 3.51 0 0016.964 12l-.71-.71a1.003 1.003 0 010-1.417l.71-.71a1.003 1.003 0 011.418 0l.709.71a6.518 6.518 0 11-9.218 9.218l-.71-.71zm0-9.218a1.504 1.504 0 012.127 0l3.545 3.545a1.504 1.504 0 01-2.127 2.127l-3.545-3.545a1.504 1.504 0 010-2.127z" fillRule="evenodd"></path></svg>
<Text ml="4" layerStyle="textSecondary"></Text>
</Box>
<Box display="flex" flexDirection="row" alignItems="center">
<svg width="48px" height="48px" fill={useColorModeValue("teal", "white")} version="1.1" viewBox="0 0 24 24"><path d="M9 2v1a1 1 0 001 1h4a1 1 0 001-1V2h1a2 2 0 012 2v16a2 2 0 01-2 2H8a2 2 0 01-2-2V4a2 2 0 012-2h1zm1 16a1 1 0 000 2h4a1 1 0 000-2h-4z" fillRule="evenodd"></path></svg>
<Text ml="4" layerStyle="textSecondary"></Text>
</Box>
<Box display="flex" flexDirection="row" alignItems="center">
<svg width="48px" height="48px" fill={useColorModeValue("teal", "white")} version="1.1" viewBox="0 0 24 24"><path d="M19 21l-3-.5.786-4.321A1 1 0 0015.802 15H8.198a1 1 0 00-.984 1.179L8 20.5 5 21a1 1 0 01-1-1v-.5c0-4.142 3.582-7.5 8-7.5s8 3.358 8 7.5v.5a1 1 0 01-1 1zm-7-2a1 1 0 110-2 1 1 0 010 2zm0-8a4 4 0 110-8 4 4 0 010 8z" fillRule="evenodd"></path></svg>
<Text ml="4" layerStyle="textSecondary"></Text>
</Box>
</VStack>
<HStack mt="6" spacing="3">
<Button onClick={() => login('cto@188.com')} layerStyle="colorButton" fontSize=".9rem" leftIcon={<FaGithub fontSize="1.0rem" />}>使github</Button>
<Text layerStyle="textSecondary">OR</Text>
<IconButton layerStyle="textSecondary" variant="outline" aria-label="login with email" icon={<FaEnvelope />} onClick={onOpen}/>
</HStack>
<Text mt="6" fontSize=".7rem" layerStyle="textSecondary">im.dev<Link textDecoration="underline"></Link><Link textDecoration="underline"></Link></Text>
{/* <Image src="/pokeman.svg" height="300px" /> */}
{
emailLogined ?
<HStack className="bordered" p="4" spacing="4" mt="4">
<FaRegPaperPlane fontSize="1.6rem" color="teal" />
<Text fontSize="1.2rem">Check your inbox for a secure link to sign in.</Text>
</HStack> :
<>
<VStack mt="2" p="5" align="left" spacing="2" fontSize="15px">
<Box display="flex" flexDirection="row" alignItems="center">
<svg width="48px" height="48px" fill={useColorModeValue("teal", "white")} version="1.1" viewBox="0 0 24 24"><path d="M7.036 14.836a1.003 1.003 0 01-1.418 0l-.709-.709a6.518 6.518 0 119.218-9.218l.71.71a1.003 1.003 0 010 1.417l-.71.71a1.003 1.003 0 01-1.418 0L12 7.035A3.51 3.51 0 007.036 12l.71.71a1.003 1.003 0 010 1.417l-.71.71zm2.128 3.546a1.003 1.003 0 010-1.418l.709-.71a1.003 1.003 0 011.418 0l.709.71A3.51 3.51 0 0016.964 12l-.71-.71a1.003 1.003 0 010-1.417l.71-.71a1.003 1.003 0 011.418 0l.709.71a6.518 6.518 0 11-9.218 9.218l-.71-.71zm0-9.218a1.504 1.504 0 012.127 0l3.545 3.545a1.504 1.504 0 01-2.127 2.127l-3.545-3.545a1.504 1.504 0 010-2.127z" fillRule="evenodd"></path></svg>
<Text ml="4" layerStyle="textSecondary"></Text>
</Box>
<Box display="flex" flexDirection="row" alignItems="center">
<svg width="48px" height="48px" fill={useColorModeValue("teal", "white")} version="1.1" viewBox="0 0 24 24"><path d="M9 2v1a1 1 0 001 1h4a1 1 0 001-1V2h1a2 2 0 012 2v16a2 2 0 01-2 2H8a2 2 0 01-2-2V4a2 2 0 012-2h1zm1 16a1 1 0 000 2h4a1 1 0 000-2h-4z" fillRule="evenodd"></path></svg>
<Text ml="4" layerStyle="textSecondary"></Text>
</Box>
<Box display="flex" flexDirection="row" alignItems="center">
<svg width="48px" height="48px" fill={useColorModeValue("teal", "white")} version="1.1" viewBox="0 0 24 24"><path d="M19 21l-3-.5.786-4.321A1 1 0 0015.802 15H8.198a1 1 0 00-.984 1.179L8 20.5 5 21a1 1 0 01-1-1v-.5c0-4.142 3.582-7.5 8-7.5s8 3.358 8 7.5v.5a1 1 0 01-1 1zm-7-2a1 1 0 110-2 1 1 0 010 2zm0-8a4 4 0 110-8 4 4 0 010 8z" fillRule="evenodd"></path></svg>
<Text ml="4" layerStyle="textSecondary"></Text>
</Box>
</VStack>
<HStack mt="6" spacing="3">
<Button onClick={() => login('cto@188.com')} layerStyle="colorButton" fontSize=".9rem" leftIcon={<FaGithub fontSize="1.0rem" />}>使github</Button>
<Text layerStyle="textSecondary">OR</Text>
<IconButton layerStyle="textSecondary" variant="outline" aria-label="login with email" icon={<FaEnvelope />} onClick={onOpen} />
</HStack>
<Text mt="6" fontSize=".7rem" layerStyle="textSecondary">im.dev<Link textDecoration="underline"></Link><Link textDecoration="underline"></Link></Text>
</>
}
</Box>
<Modal isOpen={isOpen} onClose={onClose} isCentered>
<ModalOverlay />
<ModalContent mt="0" px="10" py="6">
<ModalContent mt="0" px="10" py="6">
<ModalBody>
<VStack alignItems="left" spacing="5">
<Text layerStyle="textSecondary" fontWeight="550">Sign in using a secure link</Text>

@ -2,11 +2,6 @@ import {
Heading, HStack, Text, VStack,
Divider,
Wrap,
Image,
useColorModeValue,
Box,
StackDivider,
Tag
} from "@chakra-ui/react"
import SEO from "components/seo"
import siteConfig from "configs/site-config"
@ -14,19 +9,8 @@ import {
import React, { useEffect, useState } from "react"
import { IndexSidebar } from 'pages/index'
import Card from "components/card"
import { config } from "configs/config"
import { requestApi } from "utils/axios/request"
import { Story } from "src/types/story"
import { find } from "lodash"
import Empty from "components/empty"
import StoryCard from "components/story/story-card"
import { FaBell } from "react-icons/fa"
import { getSvgIcon } from "components/svg-icon"
import { Notification } from "src/types/notification"
import { getUserName } from "utils/user"
import moment from 'moment'
import userCustomTheme from "theme/user-custom"
import Link from "next/link"
import Notifications from "components/notifications"
const filters = [
@ -34,7 +18,7 @@ const filters = [
{icon: 'comments',label:'Comments',type: 1},
{icon: 'favorites',label:'Likes', type: 2},
{icon: 'follow',label:'Follows', type: 5},
{icon: 'at',label: 'Mentions', type: 3},
// {icon: 'at',label: 'Mentions', type: 3},
{icon: 'post',label: 'Stories', type: 4},
]

@ -0,0 +1,59 @@
package email
import (
"fmt"
"html/template"
"net/smtp"
"strings"
"github.com/jordan-wright/email"
)
var MailTemplates *template.Template
func init() {
MailTemplates = template.New("im.dev")
tmpl, err := template.ParseGlob("./server/internal/email/templates/*.tmpl")
if err != nil {
panic(err)
}
MailTemplates = tmpl
}
type EmailContent struct {
To []string
Template string
Subject string
Data map[string]interface{}
}
type EmailMessage struct {
To []string
From string
Subject string
Body string
}
func Send(msg *EmailMessage) error {
e := email.NewEmail()
for _, to := range msg.To {
e.From = msg.From
e.To = []string{to}
e.Subject = msg.Subject
e.HTML = []byte(msg.Body)
fmt.Println(e.To)
r := strings.Split(to, "@")
s := fmt.Sprintf("smtp.%s:25", r[1])
fmt.Println("smtp:", s)
err := e.Send(s, smtp.PlainAuth("", "61087682@qq.com", "nybusxktxfyycahh", "smtp.qq.com"))
if err != nil {
return err
}
}
return nil
}

@ -0,0 +1,991 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width"/>
</head>
<body leftmargin="0" topmargin="0" marginwidth="0" marginheight="0" class="main" style="background: #2e2e2e; height: 100% !important; margin: 0 auto; padding: 0; width: 100% !important" bgcolor="#2e2e2e">
<style type="text/css">
body {
height: 100% !important; width: 100% !important;
}
body .copy {
-ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
}
.ExternalClass {
width: 100%;
}
.ExternalClass {
line-height: 100%;
}
img {
-ms-interpolation-mode: bicubic;
}
img {
border: 0 !important; outline: none !important; text-decoration: none !important;
}
a:hover {
text-decoration: underline;
}
td[class="stack-column-center"] {
text-align: center !important;
}
@media (max-width: 600px) {
table[class="email-container"] {
width: 95% !important;
}
img[class="fluid"] {
width: 100% !important; max-width: 100% !important; height: auto !important; margin: auto !important;
}
img[class="fluid-centered"] {
width: 100% !important; max-width: 100% !important; height: auto !important; margin: auto !important;
}
img[class="fluid-centered"] {
margin: auto !important;
}
td[class="comms-content"] {
padding: 20px !important;
}
td[class="stack-column"] {
display: block !important; width: 100% !important; direction: ltr !important;
}
td[class="stack-column-center"] {
display: block !important; width: 100% !important; direction: ltr !important;
}
td[class="stack-column-center"] {
text-align: center !important;
}
td[class="copy"] {
font-size: 14px !important; line-height: 24px !important; padding: 0 30px !important;
}
td[class="copy -center"] {
font-size: 14px !important; line-height: 24px !important; padding: 0 30px !important;
}
td[class="copy -bold"] {
font-size: 14px !important; line-height: 24px !important; padding: 0 30px !important;
}
td[class="small-text"] {
font-size: 14px !important; line-height: 24px !important; padding: 0 30px !important;
}
td[class="mini-centered-text"] {
font-size: 14px !important; line-height: 24px !important; padding: 15px 30px !important;
}
td[class="copy -padd"] {
padding: 0 40px !important;
}
span[class="sep"] {
display: none !important;
}
td[class="mb-hide"] {
display: none !important; height: 0 !important;
}
td[class="spacer mb-shorten"] {
height: 25px !important;
}
.two-up td {
width:270px;
}
}
</style>
<style type="text/css">
body, table.body, h1, h2, h3, h4, h5, h6, p, td {
font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
}
h1 {font-size: 40px;}
h2 {font-size: 36px;}
h3 {
font-size: 22px;
margin-top: 10px;
margin-bottom: 10px;
}
h4 {font-size: 20px;}
h5 {font-size: 18px;}
h6 {font-size: 16px;}
.emphasis {
font-weight: 600;
}
a {
color: #E67612;
text-decoration: none;
}
a:hover {
color: #ff8f2b !important;
}
a:active {
color: #F2821E !important;
}
a:visited {
color: #E67612 !important;
}
table.facebook td {
background: #3b5998;
border-color: #2d4473;
}
table.facebook:hover td {
background: #2d4473 !important;
}
table.twitter td {
background: #00acee;
border-color: #0087bb;
}
table.twitter:hover td {
background: #0087bb !important;
}
table.google-plus td {
background-color: #DB4A39;
border-color: #CC0000;
}
table.google-plus:hover td {
background: #CC0000 !important;
}
.template-label {
color: #ffffff;
font-weight: bold;
font-size: 11px;
}
.callout .wrapper {
padding-bottom: 20px;
}
.callout .panel {
background: #ECF8FF;
border-color: #b9e5ff;
}
.header {
}
.data {
font-size: 16px;
}
.footer {
background-color: #2e2e2e;
color: #999999;
margin-top: 20px;
}
@media only screen and (max-width: 600px) {
table[class="body"] .right-text-pad {
padding-left: 10px !important;
}
table[class="body"] .left-text-pad {
padding-right: 10px !important;
}
.logo {
margin-left: 10px;
}
}
table.better-button {
margin-top: 10px;
margin-bottom: 20px;
}
table.columns td.better-button {
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
padding-bottom: 0px;
}
.better-button a {
text-decoration: none;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
padding: 12px 25px;
border: 1px solid #ff8f2b;
display: inline-block;
color: #FFF;
}
.better-button:hover a {
color: #FFFFFF !important;
background-color: #F2821E;
border: 1px solid #F2821E;
}
.better-button:visited a {
color: #FFFFFF !important;
}
.better-button:active a {
color: #FFFFFF !important;
}
table.better-button-alt {
margin-top: 10px;
margin-bottom: 20px;
}
table.columns td.better-button-alt {
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
padding-bottom: 0px;
}
.better-button-alt a {
text-decoration: none;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
padding: 12px 25px;
border: 1px solid #ff8f2b;
background-color: #ff8f2b;
display: inline-block;
color: white;
}
.better-button-alt:hover a {
color: #ff8f2b !important;
background-color: #DDDDDD;
border: 1px solid #F2821E;
}
.better-button-alt:visited a {
color: #ff8f2b !important;
}
.better-button-alt:active a {
color: #ff8f2b !important;
}
.verification-code {
background-color: #EEEEEE;
padding: 3px;
margin: 8px;
display: inline-block;
font-weight: bold;
font-size: 20px;
}
</style>
<style type="text/css">
#outlook a {
padding:0;
}
body{
width:100% !important;
min-width: 100%;
-webkit-text-size-adjust:100%;
-ms-text-size-adjust:100%;
margin:0;
padding:0;
}
.ExternalClass {
width:100%;
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
#backgroundTable {
margin:0;
padding:0;
width:100% !important;
line-height: 100% !important;
}
img {
outline:none;
text-decoration:none;
-ms-interpolation-mode: bicubic;
width: auto;
float: left;
clear: both;
display: block;
}
center {
width: 100%;
min-width: 580px;
}
a img {
border: none;
}
p {
margin: 0 0 0 10px;
}
table {
border-spacing: 0;
border-collapse: collapse;
}
td {
word-break: break-word;
-webkit-hyphens: auto;
-moz-hyphens: auto;
hyphens: auto;
border-collapse: collapse !important;
}
table, tr, td {
padding: 0;
vertical-align: top;
text-align: left;
}
hr {
color: #d9d9d9;
background-color: #d9d9d9;
height: 1px;
border: none;
}
/* Responsive Grid */
table.body {
height: 100%;
width: 100%;
}
table.container {
width: 580px;
margin: 0 auto;
text-align: inherit;
}
table.row {
padding: 0px;
width: 100%;
position: relative;
}
table.container table.row {
display: block;
}
td.wrapper {
padding: 10px 20px 0px 0px;
position: relative;
}
table.columns,
table.column {
margin: 0 auto;
}
table.columns td,
table.column td {
padding: 0px 0px 10px;
}
table.columns td.sub-columns,
table.column td.sub-columns,
table.columns td.sub-column,
table.column td.sub-column {
padding-right: 10px;
}
td.sub-column, td.sub-columns {
min-width: 0px;
}
table.row td.last,
table.container td.last {
padding-right: 0px;
}
table.one { width: 30px; }
table.two { width: 80px; }
table.three { width: 130px; }
table.four { width: 180px; }
table.five { width: 230px; }
table.six { width: 280px; }
table.seven { width: 330px; }
table.eight { width: 380px; }
table.nine { width: 430px; }
table.ten { width: 480px; }
table.eleven { width: 530px; }
table.twelve { width: 580px; }
table.one center { min-width: 30px; }
table.two center { min-width: 80px; }
table.three center { min-width: 130px; }
table.four center { min-width: 180px; }
table.five center { min-width: 230px; }
table.six center { min-width: 280px; }
table.seven center { min-width: 330px; }
table.eight center { min-width: 380px; }
table.nine center { min-width: 430px; }
table.ten center { min-width: 480px; }
table.eleven center { min-width: 530px; }
table.twelve center { min-width: 580px; }
table.one .panel center { min-width: 10px; }
table.two .panel center { min-width: 60px; }
table.three .panel center { min-width: 110px; }
table.four .panel center { min-width: 160px; }
table.five .panel center { min-width: 210px; }
table.six .panel center { min-width: 260px; }
table.seven .panel center { min-width: 310px; }
table.eight .panel center { min-width: 360px; }
table.nine .panel center { min-width: 410px; }
table.ten .panel center { min-width: 460px; }
table.eleven .panel center { min-width: 510px; }
table.twelve .panel center { min-width: 560px; }
.body .columns td.one,
.body .column td.one { width: 8.333333%; }
.body .columns td.two,
.body .column td.two { width: 16.666666%; }
.body .columns td.three,
.body .column td.three { width: 25%; }
.body .columns td.four,
.body .column td.four { width: 33.333333%; }
.body .columns td.five,
.body .column td.five { width: 41.666666%; }
.body .columns td.six,
.body .column td.six { width: 50%; }
.body .columns td.seven,
.body .column td.seven { width: 58.333333%; }
.body .columns td.eight,
.body .column td.eight { width: 66.666666%; }
.body .columns td.nine,
.body .column td.nine { width: 75%; }
.body .columns td.ten,
.body .column td.ten { width: 83.333333%; }
.body .columns td.eleven,
.body .column td.eleven { width: 91.666666%; }
.body .columns td.twelve,
.body .column td.twelve { width: 100%; }
td.offset-by-one { padding-left: 50px; }
td.offset-by-two { padding-left: 100px; }
td.offset-by-three { padding-left: 150px; }
td.offset-by-four { padding-left: 200px; }
td.offset-by-five { padding-left: 250px; }
td.offset-by-six { padding-left: 300px; }
td.offset-by-seven { padding-left: 350px; }
td.offset-by-eight { padding-left: 400px; }
td.offset-by-nine { padding-left: 450px; }
td.offset-by-ten { padding-left: 500px; }
td.offset-by-eleven { padding-left: 550px; }
td.expander {
visibility: hidden;
width: 0px;
padding: 0 !important;
}
table.columns .text-pad,
table.column .text-pad {
padding-left: 10px;
padding-right: 10px;
}
table.columns .left-text-pad,
table.columns .text-pad-left,
table.column .left-text-pad,
table.column .text-pad-left {
padding-left: 10px;
}
table.columns .right-text-pad,
table.columns .text-pad-right,
table.column .right-text-pad,
table.column .text-pad-right {
padding-right: 10px;
}
/* Block Grid */
.block-grid {
width: 100%;
max-width: 580px;
}
.block-grid td {
display: inline-block;
padding:10px;
}
.two-up td {
width:270px;
}
.three-up td {
width:173px;
}
.four-up td {
width:125px;
}
.five-up td {
width:96px;
}
.six-up td {
width:76px;
}
.seven-up td {
width:62px;
}
.eight-up td {
width:52px;
}
/* Alignment & Visibility Classes */
table.center, td.center {
text-align: center;
}
h1.center,
h2.center,
h3.center,
h4.center,
h5.center,
h6.center {
text-align: center;
}
span.center {
display: block;
width: 100%;
text-align: center;
}
img.center {
margin: 0 auto;
float: none;
}
.show-for-small,
.hide-for-desktop {
display: none;
}
/* Typography */
body, table.body, h1, h2, h3, h4, h5, h6, p, td {
color: #222222;
font-family: "Helvetica", "Arial", sans-serif;
font-weight: normal;
padding:0;
margin: 0;
text-align: left;
line-height: 1.3;
}
h1, h2, h3, h4, h5, h6 {
word-break: normal;
}
h1 {font-size: 40px;}
h2 {font-size: 36px;}
h3 {font-size: 32px;}
h4 {font-size: 28px;}
h5 {font-size: 24px;}
h6 {font-size: 20px;}
body, table.body, p, td {font-size: 14px;line-height:19px;}
p.lead, p.lede, p.leed {
font-size: 18px;
line-height:21px;
}
p {
margin-bottom: 10px;
}
small {
font-size: 10px;
}
a {
color: #2ba6cb;
text-decoration: none;
}
a:hover {
color: #2795b6 !important;
}
a:active {
color: #2795b6 !important;
}
a:visited {
color: #2ba6cb !important;
}
h1 a,
h2 a,
h3 a,
h4 a,
h5 a,
h6 a {
color: #2ba6cb;
}
h1 a:active,
h2 a:active,
h3 a:active,
h4 a:active,
h5 a:active,
h6 a:active {
color: #2ba6cb !important;
}
h1 a:visited,
h2 a:visited,
h3 a:visited,
h4 a:visited,
h5 a:visited,
h6 a:visited {
color: #2ba6cb !important;
}
/* Panels */
.panel {
background: #f2f2f2;
border: 1px solid #d9d9d9;
padding: 10px !important;
}
.sub-grid table {
width: 100%;
}
.sub-grid td.sub-columns {
padding-bottom: 0;
}
/* Buttons */
table.button,
table.tiny-button,
table.small-button,
table.medium-button,
table.large-button {
width: 100%;
overflow: hidden;
}
table.button td,
table.tiny-button td,
table.small-button td,
table.medium-button td,
table.large-button td {
display: block;
width: auto !important;
text-align: center;
background: #2ba6cb;
border: 1px solid #2284a1;
color: #ffffff;
padding: 8px 0;
}
table.tiny-button td {
padding: 5px 0 4px;
}
table.small-button td {
padding: 8px 0 7px;
}
table.medium-button td {
padding: 12px 0 10px;
}
table.large-button td {
padding: 21px 0 18px;
}
table.button td a,
table.tiny-button td a,
table.small-button td a,
table.medium-button td a,
table.large-button td a {
font-weight: bold;
text-decoration: none;
font-family: Helvetica, Arial, sans-serif;
color: #ffffff;
font-size: 16px;
}
table.tiny-button td a {
font-size: 12px;
font-weight: normal;
}
table.small-button td a {
font-size: 16px;
}
table.medium-button td a {
font-size: 20px;
}
table.large-button td a {
font-size: 24px;
}
table.button:hover td,
table.button:visited td,
table.button:active td {
background: #2795b6 !important;
}
table.button:hover td a,
table.button:visited td a,
table.button:active td a {
color: #fff !important;
}
table.button:hover td,
table.tiny-button:hover td,
table.small-button:hover td,
table.medium-button:hover td,
table.large-button:hover td {
background: #2795b6 !important;
}
table.button:hover td a,
table.button:active td a,
table.button td a:visited,
table.tiny-button:hover td a,
table.tiny-button:active td a,
table.tiny-button td a:visited,
table.small-button:hover td a,
table.small-button:active td a,
table.small-button td a:visited,
table.medium-button:hover td a,
table.medium-button:active td a,
table.medium-button td a:visited,
table.large-button:hover td a,
table.large-button:active td a,
table.large-button td a:visited {
color: #ffffff !important;
}
table.secondary td {
background: #e9e9e9;
border-color: #d0d0d0;
color: #555;
}
table.secondary td a {
color: #555;
}
table.secondary:hover td {
background: #d0d0d0 !important;
color: #555;
}
table.secondary:hover td a,
table.secondary td a:visited,
table.secondary:active td a {
color: #555 !important;
}
table.success td {
background: #5da423;
border-color: #457a1a;
}
table.success:hover td {
background: #457a1a !important;
}
table.alert td {
background: #c60f13;
border-color: #970b0e;
}
table.alert:hover td {
background: #970b0e !important;
}
table.radius td {
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
table.round td {
-webkit-border-radius: 500px;
-moz-border-radius: 500px;
border-radius: 500px;
}
/* Outlook First */
body.outlook p {
display: inline !important;
}
/* Media Queries */
@media only screen and (max-width: 600px) {
table[class="body"] img {
}
table[class="body"] center {
min-width: 0 !important;
}
table[class="body"] .container {
width: 95% !important;
}
table[class="body"] .row {
width: 100% !important;
display: block !important;
}
table[class="body"] .wrapper {
display: block !important;
padding-right: 0 !important;
}
table[class="body"] .columns,
table[class="body"] .column {
table-layout: fixed !important;
float: none !important;
width: 100% !important;
padding-right: 0px !important;
padding-left: 0px !important;
display: block !important;
}
table[class="body"] .wrapper.first .columns,
table[class="body"] .wrapper.first .column {
display: table !important;
}
table[class="body"] table.columns td,
table[class="body"] table.column td {
width: 100% !important;
}
table[class="body"] .columns td.one,
table[class="body"] .column td.one { width: 8.333333% !important; }
table[class="body"] .columns td.two,
table[class="body"] .column td.two { width: 16.666666% !important; }
table[class="body"] .columns td.three,
table[class="body"] .column td.three { width: 25% !important; }
table[class="body"] .columns td.four,
table[class="body"] .column td.four { width: 33.333333% !important; }
table[class="body"] .columns td.five,
table[class="body"] .column td.five { width: 41.666666% !important; }
table[class="body"] .columns td.six,
table[class="body"] .column td.six { width: 50% !important; }
table[class="body"] .columns td.seven,
table[class="body"] .column td.seven { width: 58.333333% !important; }
table[class="body"] .columns td.eight,
table[class="body"] .column td.eight { width: 66.666666% !important; }
table[class="body"] .columns td.nine,
table[class="body"] .column td.nine { width: 75% !important; }
table[class="body"] .columns td.ten,
table[class="body"] .column td.ten { width: 83.333333% !important; }
table[class="body"] .columns td.eleven,
table[class="body"] .column td.eleven { width: 91.666666% !important; }
table[class="body"] .columns td.twelve,
table[class="body"] .column td.twelve { width: 100% !important; }
table[class="body"] td.offset-by-one,
table[class="body"] td.offset-by-two,
table[class="body"] td.offset-by-three,
table[class="body"] td.offset-by-four,
table[class="body"] td.offset-by-five,
table[class="body"] td.offset-by-six,
table[class="body"] td.offset-by-seven,
table[class="body"] td.offset-by-eight,
table[class="body"] td.offset-by-nine,
table[class="body"] td.offset-by-ten,
table[class="body"] td.offset-by-eleven {
padding-left: 0 !important;
}
table[class="body"] table.columns td.expander {
width: 1px !important;
}
table[class="body"] .right-text-pad,
table[class="body"] .text-pad-right {
padding-left: 10px !important;
}
table[class="body"] .left-text-pad,
table[class="body"] .text-pad-left {
padding-right: 10px !important;
}
table[class="body"] .hide-for-small,
table[class="body"] .show-for-desktop {
display: none !important;
}
table[class="body"] .show-for-small,
table[class="body"] .hide-for-desktop {
display: inherit !important;
}
}
</style>
<table class="body" style="background: #2e2e2e;">
<tr>
<td class="center" align="center" valign="top">
<center>
<table class="container" style="background-color: #efefef;" width="600">
<tr>
<td height="2" class="spacer mb-shorten" style="font-size: 0; line-height: 0; mso-table-lspace: 0pt; mso-table-rspace: 0pt; vertical-align: top; border: 0; background: linear-gradient(to right, #ffed00 0%, #f26529 75%); height: 2px !important" valign="top"> </td>
</tr>
<tr>
<td class="mini-centered-text" style="color: #343b41; font: 400 16px/27px 'Helvetica Neue', Helvetica, Arial, sans-serif; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding: 25px 35px; text-align: center; vertical-align: top" align="center" valign="top">
{{block "content" . }}{{end}}
<!-- container end below -->
</td>
</tr>
</table>
</center>
</td>
</tr>
</table>
</body>
</html>

@ -0,0 +1,70 @@
{{template "base.tmpl" .}}
{{define "content"}}
<table class="row">
<tr>
<td class="wrapper last">
<table class="twelve columns">
<tr>
<td class="center">
<h3 style="font-weight: bold; font-style: italic;">{{.Title}}</h3>
</td>
</tr>
</table>
</td>
</tr>
</table>
<table class="row" >
<tr>
<td class="last">
<table class="twelve columns">
<tr>
<td class="center">
<p style="/*text-align:center*/">Use the following link to sign up or automatically login to Hashnode, be careful who you give it to.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
<table class="row">
<tr>
<td class="wrapper last">
<table class="twelve columns">
<tr>
<td class="center six">
<table class="better-button" align="center" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" class="better-button-alt" bgcolor="#efefef">
<a href="{{.loginURL}}" target="_blank">Click here to sign in</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
<table class="row" >
<tr>
<td class="last">
<table class="twelve columns">
<tr>
<td class="center">
<p style="/*text-align:center*/">Or copy this url into your browser</p>
<p>{{.URL}}</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
{{end}}

@ -91,6 +91,10 @@ func Query(user *models.User, tp int, page int) ([]*models.Notification, *e.Erro
}
case models.NotificationFollow:
no.Title = " started following you"
case models.NotificationPublish:
no.Title = " published a new story"
no.SubTitle = models.GetStoryTitle(noID)
no.StoryID = noID
}
nos = append(nos, no)
}

@ -96,6 +96,7 @@ func (s *Server) Start() error {
r.GET("/user/posts/:userID", api.GetUserPosts)
r.GET("/user/session", api.GetSession)
r.POST("/user/login", user.Login)
r.POST("/user/login/email", user.LoginEmail)
r.POST("/user/logout", user.Logout)
r.POST("/user/navbar", IsLogin(), api.SubmitUserNavbar)
r.GET("/user/navbars/:userID", api.GetUserNavbars)

@ -108,6 +108,7 @@ func SubmitStory(c *gin.Context) (map[string]string, *e.Error) {
logger.Warn("submit post error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
} else {
post.Updated = now
// 只有创建者自己才能更新内容
@ -121,6 +122,24 @@ func SubmitStory(c *gin.Context) (map[string]string, *e.Error) {
// 首次发布,需要更新创建时间
_, err = db.Conn.Exec("UPDATE story SET owner=?, slug=?, title=?, md=?, url=?, cover=?, brief=?,created=?,updated=?,status=? WHERE id=?",
post.OwnerID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, post.Created, post.Updated, models.StatusPublished, post.ID)
// send notification to creator followers and org followers
followers, err1 := interaction.GetFollowerIDs(post.CreatorID)
if err1 == nil {
for _, f := range followers {
notification.Send(f, "", models.NotificationPublish, post.ID, post.CreatorID)
}
}
if post.OwnerID != "" {
followers, err1 = interaction.GetFollowerIDs(post.OwnerID)
if err1 == nil {
for _, f := range followers {
notification.Send("", f, models.NotificationPublish, post.ID, post.CreatorID)
}
}
}
} else {
_, err = db.Conn.Exec("UPDATE story SET owner=?, slug=?, title=?, md=?, url=?, cover=?, brief=?, updated=? WHERE id=?",
post.OwnerID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, post.Updated, post.ID)
@ -142,23 +161,6 @@ func SubmitStory(c *gin.Context) (map[string]string, *e.Error) {
likes := interaction.GetLikes(post.ID)
top.Update(post.ID, likes)
// send notification to creator followers and org followers
followers, err1 := interaction.GetFollowerIDs(post.CreatorID)
if err1 == nil {
for _, f := range followers {
notification.Send(f, "", models.NotificationFollow, post.ID, post.CreatorID)
}
}
if post.OwnerID != "" {
followers, err1 = interaction.GetFollowerIDs(post.OwnerID)
if err1 == nil {
for _, f := range followers {
notification.Send("", f, models.NotificationFollow, post.ID, post.CreatorID)
}
}
}
return map[string]string{
"username": user.Username,
"id": post.ID,

@ -1,12 +1,16 @@
package user
import (
"bytes"
"database/sql"
"fmt"
"net/http"
"strconv"
"time"
"github.com/asaskevich/govalidator"
"github.com/gin-gonic/gin"
"github.com/imdotdev/im.dev/server/internal/email"
"github.com/imdotdev/im.dev/server/pkg/common"
"github.com/imdotdev/im.dev/server/pkg/config"
"github.com/imdotdev/im.dev/server/pkg/db"
@ -61,6 +65,77 @@ func Login(c *gin.Context) {
c.JSON(http.StatusOK, common.RespSuccess(session))
}
func LoginEmail(c *gin.Context) {
user := &models.User{}
c.Bind(&user)
if !govalidator.IsEmail(user.Email) {
c.JSON(http.StatusBadRequest, common.RespError("邮件格式不合法"))
return
}
fmt.Println(user.Email)
var buffer bytes.Buffer
err := email.MailTemplates.ExecuteTemplate(&buffer, "login.tmpl", map[string]string{
"Title": "Im.dev",
"URL": "https://im.dev/login",
})
if err != nil {
logger.Warn("login with email error", "error", err, "email", user.Email)
c.JSON(http.StatusInternalServerError, common.RespInternalError())
return
}
emailMsg := &email.EmailMessage{
To: []string{user.Email},
From: fmt.Sprintf("%s <%s>", config.Data.SMTP.FromName, "61087682@qq.com"),
Subject: fmt.Sprintf("Sign in to %s", config.Data.Common.AppName),
Body: buffer.String(),
}
err = email.Send(emailMsg)
if err != nil {
logger.Warn("send login email error", "error", err)
c.JSON(http.StatusBadRequest, common.RespError(err.Error()))
return
}
// err := user.Query("", "", user.Email)
// if err != nil {
// if err == sql.ErrNoRows {
// c.JSON(http.StatusNotFound, common.RespError("邮箱不存在"))
// return
// }
// logger.Error("login error", "error", err)
// c.JSON(http.StatusInternalServerError, common.RespInternalError())
// return
// }
// // delete old session
// token := getToken(c)
// deleteSession(token)
// token = strconv.FormatInt(time.Now().UnixNano(), 10)
// session := &Session{
// Token: token,
// User: user,
// CreateTime: time.Now(),
// }
// err = storeSession(session)
// if err != nil {
// c.JSON(500, common.RespInternalError())
// return
// }
// _, err = db.Conn.Exec(`UPDATE user SET last_seen_at=? WHERE id=?`, time.Now(), user.ID)
// if err != nil {
// logger.Warn("set last login date error", "error", err)
// }
c.JSON(http.StatusOK, common.RespSuccess(nil))
}
// Logout ...
func Logout(c *gin.Context) {
token := getToken(c)

@ -43,6 +43,11 @@ type Config struct {
BriefMaxLen int `yaml:"brief_max_len"`
WritingEnabled bool `yaml:"writing_enabled"`
}
SMTP struct {
FromAddr string `yaml:"from_address"`
FromName string `yaml:"from_name"`
}
}
// Data ...

Loading…
Cancel
Save